<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>중요한 건 방향과 꾸준함</title>
    <link>https://storyofhz.tistory.com/</link>
    <description>침착해 다른 달팽이들은 신경쓰지 말고 네가 가야 할 곳에 집중해야해</description>
    <language>ko</language>
    <pubDate>Wed, 13 May 2026 03:03:25 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>Joy Shin</managingEditor>
    <image>
      <title>중요한 건 방향과 꾸준함</title>
      <url>https://tistory1.daumcdn.net/tistory/7145989/attach/6ee72fa38fa04f79b849ad2eae88a502</url>
      <link>https://storyofhz.tistory.com</link>
    </image>
    <item>
      <title>llama.cpp 를 활용해 sLLM 테스트 하기</title>
      <link>https://storyofhz.tistory.com/21</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;sLLM을 로컬에서 테스트 해볼 때, 사용해볼 수 있는 도구들이 꽤 다양하단 걸 이번 기회에 알게 되었다. (ollama, llama-cpp, lm studio,...)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에 ollama를 사용해 embedding model을 사용했던터라, 기존에 설치 되어있던 ollama를 그대로 이번 테스트에 사용해보려고 했는데, 처리 속도가 생각보다 너무 느렸기에 대안이 필요했다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;STT로 전사된 텍스트를 LLM을 사용해 후처리로 실시간 수정하는 것을 목표로 , 어느 sLLM 모델을 사용해야할지 찾아보는 테스트였기 때문에, 추론 비용(속도, 메모리 사용량 등)이 중요한 이슈였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ollama가 llama.cpp를 래핑한 도구이기에 같은 모델을 로컬에서 돌리더라도, llama.cpp는 ollama보다 추론이 훨씬 빠르다는 글을 보고 llama.cpp를 사용해 테스트를 진행하게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떻게 llama.cpp를 사용해 sLLM을 로컬에서 테스트를 해보았는지 그 과정을 기록한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;llama.cpp? llama.cpp-python?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;llama.cpp를 python 라이브러리로 구현해놓은 llama.cpp-python도 존재한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;llama.cpp&lt;/code&gt;: &lt;a href=&quot;https://github.com/ggml-org/llama.cpp&quot;&gt;https://github.com/ggml-org/llama.cpp&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;llama.cpp-python&lt;/code&gt;: &lt;a href=&quot;https://github.com/abetlen/llama-cpp-python&quot;&gt;https://github.com/abetlen/llama-cpp-python&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;python을 주로 사용하는 사용자라면, 라이브러리 import로 간단히 llama.cpp-python을 불러와서 sLLM을 로드해 쓰는 것이 편하고 익숙한 방법일 수 있다. 나도 비슷하게 llama.cpp-python을 사용해 테스트를 하려 했으나, 결국엔 llama-cpp로 넘어오게 되었다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;왜냐하면..? llama-cpp-python에서는 모델 업데이트가 늦는 것인지, 비교적 최신 모델을 로드하려 하면 모델 구조 업데이트가 되지 않아 로드할 수 없다는 오류가 발생했다. (e.g. &lt;code&gt;exaone4&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;그러나 llama-cpp는 이런 문제 없이 새로운 LLM 모델들에 대한 업데이트가 빠르게 되어 있었으므로, llama.cpp-python이 아니라, llama-cpp를 최종적으로 사용하게 되었다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;llama.cpp는 그럼 어떻게 다운로드 받는데?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 사용한 PC는 &lt;code&gt;Window OS / GPU RTX 3060 / CUDA12.4&lt;/code&gt; 였으므로, 이 조건에 맞는 llama.cpp를 사용해야 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;갖고 있는 GPU를 사용해야 했는데,,,, 어떻게 설치해야하는지 설치법을 올린 블로그들을 전전하면서 시키는대로 하니까 내 PC에서는 설치가 안되는거다!?!!!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(나 빼고 다 그러고 있겠지만..) 공식 github부터 살펴보길 바란다. . 익숙한 한국어로 어떻게 사용하는지 찾아봐야지~ 하며 요행을 바라다가 빙빙 돌아갔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;http://%20https://github.com/ggml-org/llama.cpp&quot;&gt;llama.cpp github&lt;/a&gt;를 들어가보면 너무 친절하게도 &lt;b&gt;Pre-built Binary&lt;/b&gt; 파일들이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바로 소스 코드를 직접 빌드하지 않아도 바로 실행할 수 있게 미리 컴파일해 둔 실행 파일들이다... 친절한 주인장..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 조건에 맞는 zip 파일을 다운로드 받아서 압축 해제후 사용만 하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나의 경우는, &lt;code&gt;Windows x64 (CUDA 12) - CUDA 12.4 DLLs&lt;/code&gt; 를 다운로드 받아 압축 해제 후 사용했다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;압축 해제 후, 압축 해제 폴더 안에 models 폴더를 만들어서 내가 테스트 해주고 싶은 sLLM 모델의 gguf 버전 모델 파일들을 다운로드 해 두고 사용했다!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1802&quot; data-origin-height=&quot;986&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/z8ISV/dJMcahwfsAG/xj1JHXlv8BlKrQmOknSmc1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/z8ISV/dJMcahwfsAG/xj1JHXlv8BlKrQmOknSmc1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/z8ISV/dJMcahwfsAG/xj1JHXlv8BlKrQmOknSmc1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fz8ISV%2FdJMcahwfsAG%2Fxj1JHXlv8BlKrQmOknSmc1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;550&quot; height=&quot;301&quot; data-origin-width=&quot;1802&quot; data-origin-height=&quot;986&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1823&quot; data-origin-height=&quot;1281&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qN9cr/dJMcai2WCG5/oP6JsmrrWEGI24KK6ZqkDK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qN9cr/dJMcai2WCG5/oP6JsmrrWEGI24KK6ZqkDK/img.png&quot; data-alt=&quot;Pre-built Binaries: zip 파일 다운로드 받아서 압축 해제 후 그냥 쓰기만 하면 된다 엉엉&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qN9cr/dJMcai2WCG5/oP6JsmrrWEGI24KK6ZqkDK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqN9cr%2FdJMcai2WCG5%2FoP6JsmrrWEGI24KK6ZqkDK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;535&quot; height=&quot;376&quot; data-origin-width=&quot;1823&quot; data-origin-height=&quot;1281&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Pre-built Binaries: zip 파일 다운로드 받아서 압축 해제 후 그냥 쓰기만 하면 된다 엉엉&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1832&quot; data-origin-height=&quot;2662&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bmq1L5/dJMcaaDTmKn/rGxeGhJJf0G6pyBJ20Q2jk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bmq1L5/dJMcaaDTmKn/rGxeGhJJf0G6pyBJ20Q2jk/img.png&quot; data-alt=&quot;압축 해제하면, 이런 것들이 들어있다! llama-cli ,llama-server,...&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bmq1L5/dJMcaaDTmKn/rGxeGhJJf0G6pyBJ20Q2jk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbmq1L5%2FdJMcaaDTmKn%2FrGxeGhJJf0G6pyBJ20Q2jk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;451&quot; height=&quot;655&quot; data-origin-width=&quot;1832&quot; data-origin-height=&quot;2662&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;압축 해제하면, 이런 것들이 들어있다! llama-cli ,llama-server,...&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;llama.cpp를 어떻게 사용했나?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;llama-server 사용해 llm 로컬에 서빙하기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델을 http 서버로 띄워 놓고, 요청하는 식으로 사용했다. 이 때 사용하는게 `llama-server` 이다. 아래 처럼 원하는 모델 파일을 llama-server로 띄워놓고 Python Script에서 서버에 요청을 보내 응답을 받아오는 식으로 사용했다.&lt;/p&gt;
&lt;pre id=&quot;code_1766562401038&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;llama-server.exe \
    -m &quot;.\models\Qwen3-4B-Q4_K_M.gguf&quot; \
    -ngl 99 \
    -c 4096&lt;/code&gt;&lt;/pre&gt;
&lt;table id=&quot;2c7fb829-0348-80f5-a7c3-c55e0b56b59b&quot; style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr id=&quot;2c7fb829-0348-8003-a8a8-c26db679b8fa&quot;&gt;
&lt;td id=&quot;cKAG&quot; style=&quot;width: 11.0465%;&quot;&gt;&lt;b&gt;인자&lt;/b&gt;&lt;/td&gt;
&lt;td id=&quot;evqg&quot; style=&quot;width: 13.2558%;&quot;&gt;&lt;b&gt;설명&lt;/b&gt;&lt;/td&gt;
&lt;td id=&quot;{iF}&quot; style=&quot;width: 10.1163%;&quot;&gt;&lt;b&gt;추천 값&lt;/b&gt;&lt;/td&gt;
&lt;td id=&quot;:qax&quot; style=&quot;width: 65.4651%;&quot;&gt;&lt;b&gt;이유 및 결정 기준&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;2c7fb829-0348-8057-aaee-e7f02b7fcb5c&quot;&gt;
&lt;td id=&quot;cKAG&quot; style=&quot;width: 11.0465%;&quot;&gt;-ngl N&lt;/td&gt;
&lt;td id=&quot;evqg&quot; style=&quot;width: 13.2558%;&quot;&gt;&lt;b&gt;GPU 레이어 수&lt;/b&gt; (Number of GPU Layers)&lt;/td&gt;
&lt;td id=&quot;{iF}&quot; style=&quot;width: 10.1163%;&quot;&gt;&lt;b&gt;99&lt;/b&gt; 또는 &lt;b&gt;-1&lt;/b&gt;&lt;/td&gt;
&lt;td id=&quot;:qax&quot; style=&quot;width: 65.4651%;&quot;&gt;Qwen3-4B 모델의 레이어 전체를 &lt;b&gt;VRAM (GPU 메모리)&lt;/b&gt;에 올려서 실행 속도를 극대화합니다. 99(혹은 -1)는 &quot;가능한 모든 레이어&quot;를 의미합니다. &lt;b&gt;가장 중요한 성능 인자&lt;/b&gt;입니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;2c7fb829-0348-8043-ba57-f1633d20ec9e&quot;&gt;
&lt;td id=&quot;cKAG&quot; style=&quot;width: 11.0465%;&quot;&gt;-c N&lt;/td&gt;
&lt;td id=&quot;evqg&quot; style=&quot;width: 13.2558%;&quot;&gt;&lt;b&gt;컨텍스트 크기&lt;/b&gt; (Context Size)&lt;/td&gt;
&lt;td id=&quot;{iF}&quot; style=&quot;width: 10.1163%;&quot;&gt;&lt;b&gt;4096&lt;/b&gt;&lt;/td&gt;
&lt;td id=&quot;:qax&quot; style=&quot;width: 65.4651%;&quot;&gt;Qwen3-4B 모델이 공식적으로 지원하는 최대 컨텍스트 길이는 32K이지만, 이 값을 설정하면 &lt;b&gt;KV 캐시&lt;/b&gt;가 그만큼 커지므로 VRAM/RAM 사용량이 증가합니다. 따라서 기본적으로 4096 정도의 크기로 설정해놓고 시작했습니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;2c7fb829-0348-80bd-ba47-d83879d7ae5d&quot;&gt;
&lt;td id=&quot;cKAG&quot; style=&quot;width: 11.0465%;&quot;&gt;-t N&lt;/td&gt;
&lt;td id=&quot;evqg&quot; style=&quot;width: 13.2558%;&quot;&gt;&lt;b&gt;CPU 스레드 수&lt;/b&gt; (Number of Threads)&lt;/td&gt;
&lt;td id=&quot;{iF}&quot; style=&quot;width: 10.1163%;&quot;&gt;&lt;b&gt;8&lt;/b&gt;&lt;/td&gt;
&lt;td id=&quot;:qax&quot; style=&quot;width: 65.4651%;&quot;&gt;모델의 나머지 계산이나 CPU 영역 처리에 사용할 스레드 수입니다. 일반적으로 시스템의 &lt;b&gt;논리 코어 수의 절반&lt;/b&gt;이나 &lt;b&gt;전체 수&lt;/b&gt;를 설정합니다. (예: 8코어 CPU라면 8 또는 16)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;2c7fb829-0348-8056-992d-ea8f9bc68b46&quot;&gt;
&lt;td id=&quot;cKAG&quot; style=&quot;width: 11.0465%;&quot;&gt;--port N&lt;/td&gt;
&lt;td id=&quot;evqg&quot; style=&quot;width: 13.2558%;&quot;&gt;서버 포트 번호&lt;/td&gt;
&lt;td id=&quot;{iF}&quot; style=&quot;width: 10.1163%;&quot;&gt;8080&lt;/td&gt;
&lt;td id=&quot;:qax&quot; style=&quot;width: 65.4651%;&quot;&gt;서버가 리스닝할 포트 번호입니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;떠있는 llama-server에 요청 보내고 응답 받아오기&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1766563532041&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from openai import OpenAI

# 1. 서버 설정 (llama-server의 기본 포트 8080 사용)
client = OpenAI(
    base_url=&quot;http://localhost:8080/v1&quot;,
    api_key=&quot;not-needed&quot; # 로컬 실행 시 인증키는 필요 없으나 형식상 입력
)

# 2. 질문 보내기
# - model: 서버가 로딩 중인 모델명을 자동으로 인식하므로 아무 이름이나 써도 됩니다.
# - max_tokens: 실행 시 설정한 -c 4096 범위 내에서 적절히 조절하세요.
response = client.chat.completions.create(
    model=&quot;qwen3-4b&quot;, 
    messages=[
        {&quot;role&quot;: &quot;system&quot;, &quot;content&quot;: &quot;당신은 친절한 AI 어시스턴트입니다.&quot;},
        {&quot;role&quot;: &quot;user&quot;, &quot;content&quot;: &quot;오늘 날씨에 어울리는 노래 하나 추천해줘.&quot;}
    ],
    temperature=0.7,
    max_tokens=1024
)

# 3. 결과 출력
print(&quot;AI 답변:&quot;, response.choices[0].message.content)&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;Instruct? Q4? K? M? gguf? 암호같은 LLM 모델명을 알아보자&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;예: Qwen3-4B-Instruct-2507-Q4_K_M.gguf 모델&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1358&quot; data-origin-height=&quot;1016&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bHBgir/dJMcacoa1Sh/ciXap8CpaUQuPiKG0OJObK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bHBgir/dJMcacoa1Sh/ciXap8CpaUQuPiKG0OJObK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bHBgir/dJMcacoa1Sh/ciXap8CpaUQuPiKG0OJObK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbHBgir%2FdJMcacoa1Sh%2FciXap8CpaUQuPiKG0OJObK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;368&quot; height=&quot;275&quot; data-origin-width=&quot;1358&quot; data-origin-height=&quot;1016&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Qwen3&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span data-token-index=&quot;0&quot;&gt;모델 시리즈 이름&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;4B&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모델 크기 (뇌 용량)&lt;/li&gt;
&lt;li&gt;B = Billion (십억)&lt;/li&gt;
&lt;li&gt;4B = 약 40억 개의 파라미터&lt;/li&gt;
&lt;li&gt;4B는 경량 모델의 축에 속한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt; Instruct &lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span data-token-index=&quot;0&quot;&gt;지시를 잘 따르도록 훈련된 버전의 모델&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;span data-token-index=&quot;0&quot;&gt; 2507 &lt;/span&gt;&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span data-token-index=&quot;0&quot;&gt; &lt;span data-token-index=&quot;0&quot;&gt;모델 버전 / 릴리스 시점 (YYMM)&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;span data-token-index=&quot;0&quot;&gt;&lt;span data-token-index=&quot;0&quot;&gt; Q4 / Q2 ~ Q8 &lt;/span&gt;&lt;/span&gt;&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span data-token-index=&quot;0&quot;&gt;&lt;span data-token-index=&quot;0&quot;&gt; &lt;span data-token-index=&quot;0&quot;&gt;양자화(압축) 정도&lt;/span&gt; &lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span data-token-index=&quot;0&quot;&gt;&lt;span data-token-index=&quot;0&quot;&gt; 원래 모델은 숫자를 &lt;span data-token-index=&quot;1&quot;&gt;아주 정밀하게&lt;/span&gt; 기억함 (예: 16비트) &lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;그걸&amp;nbsp;4비트/5비트/8비트&amp;nbsp;로 간단하게 압축&lt;/li&gt;
&lt;li&gt;용량 &amp;darr;, 속도 &amp;uarr;, 대신 약간의 성능 손실&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;/b&gt; 숫자별 느낌 &lt;br /&gt;
&lt;table id=&quot;2c7fb829-0348-80ae-b7f7-cc3e5cf357b8&quot; style=&quot;border-collapse: collapse; width: 62.6885%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr id=&quot;2c7fb829-0348-8075-a037-f679ddce402b&quot;&gt;
&lt;td id=&quot;SIoQ&quot; style=&quot;width: 5.12407%;&quot;&gt;Q2&lt;/td&gt;
&lt;td id=&quot;&amp;lt;yZ?&quot; style=&quot;width: 33.3181%;&quot;&gt;아주 작음&lt;/td&gt;
&lt;td id=&quot;CG@}&quot; style=&quot;width: 23.6822%;&quot;&gt;&amp;nbsp;품질 많이 손해&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;2c7fb829-0348-8090-9def-d4004bec5250&quot;&gt;
&lt;td id=&quot;SIoQ&quot; style=&quot;width: 5.12407%;&quot;&gt;Q3&lt;/td&gt;
&lt;td id=&quot;&amp;lt;yZ?&quot; style=&quot;width: 33.3181%;&quot;&gt;작음&lt;/td&gt;
&lt;td id=&quot;CG@}&quot; style=&quot;width: 23.6822%;&quot;&gt;실험용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;2c7fb829-0348-80aa-a148-d20c75666fbc&quot;&gt;
&lt;td id=&quot;SIoQ&quot; style=&quot;width: 5.12407%;&quot;&gt;&lt;b&gt;Q4&lt;/b&gt;&lt;/td&gt;
&lt;td id=&quot;&amp;lt;yZ?&quot; style=&quot;width: 33.3181%;&quot;&gt;⭐ 균형&lt;/td&gt;
&lt;td id=&quot;CG@}&quot; style=&quot;width: 23.6822%;&quot;&gt;가장 많이 씀&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;2c7fb829-0348-80d8-964f-d9ae43d567fc&quot;&gt;
&lt;td id=&quot;SIoQ&quot; style=&quot;width: 5.12407%;&quot;&gt;Q5&lt;/td&gt;
&lt;td id=&quot;&amp;lt;yZ?&quot; style=&quot;width: 33.3181%;&quot;&gt;안정적&lt;/td&gt;
&lt;td id=&quot;CG@}&quot; style=&quot;width: 23.6822%;&quot;&gt;품질 우선&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;2c7fb829-0348-80d6-a405-e17753bb416d&quot;&gt;
&lt;td id=&quot;SIoQ&quot; style=&quot;width: 5.12407%;&quot;&gt;Q6&lt;/td&gt;
&lt;td id=&quot;&amp;lt;yZ?&quot; style=&quot;width: 33.3181%;&quot;&gt;거의 원본&lt;/td&gt;
&lt;td id=&quot;CG@}&quot; style=&quot;width: 23.6822%;&quot;&gt;메모리 큼&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;2c7fb829-0348-809b-803c-cf96dc63b613&quot;&gt;
&lt;td id=&quot;SIoQ&quot; style=&quot;width: 5.12407%;&quot;&gt;Q8&lt;/td&gt;
&lt;td id=&quot;&amp;lt;yZ?&quot; style=&quot;width: 33.3181%;&quot;&gt;원본에 매우 가까움&lt;/td&gt;
&lt;td id=&quot;CG@}&quot; style=&quot;width: 23.6822%;&quot;&gt;서버급&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt; K / S / M &amp;larr; 양자화 방식&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt; K = K-Quant 양자화 방식&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;llama.cpp 계열에서 &lt;span data-token-index=&quot;1&quot;&gt;가장 최신 &amp;amp; 똑똑한 압축&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;레이어별로 중요한 부분은 더 정밀하게 저장&lt;/li&gt;
&lt;li&gt;&lt;span data-token-index=&quot;0&quot;&gt;같은 Q4라도 품질이 더 좋음&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;Q4_K&amp;nbsp;&amp;gt;&amp;nbsp;Q4_S&amp;nbsp;&amp;gt;&amp;nbsp;Q4&amp;nbsp;(구형)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt; M / S 차이 &amp;larr; 압축 스타일&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;S vs M vs L&lt;/b&gt;
&lt;table id=&quot;2c7fb829-0348-801d-bcf0-fb9939dfaa3e&quot; style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr id=&quot;2c7fb829-0348-8086-8a5d-eb4964dca46a&quot;&gt;
&lt;td id=&quot;jxgI&quot;&gt;M&lt;/td&gt;
&lt;td id=&quot;h&amp;lt;ex&quot;&gt;Medium&lt;/td&gt;
&lt;td id=&quot;sMb[&quot;&gt;품질/속도 균형&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;2c7fb829-0348-8099-b3da-f316fc24ab07&quot;&gt;
&lt;td id=&quot;jxgI&quot;&gt;S&lt;/td&gt;
&lt;td id=&quot;h&amp;lt;ex&quot;&gt;Small&lt;/td&gt;
&lt;td id=&quot;sMb[&quot;&gt;더 작고 빠름&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;2c7fb829-0348-80bb-a092-c6d8851ba46c&quot;&gt;
&lt;td id=&quot;jxgI&quot;&gt;L&lt;/td&gt;
&lt;td id=&quot;h&amp;lt;ex&quot;&gt;Large&lt;/td&gt;
&lt;td id=&quot;sMb[&quot;&gt;품질 우선 (덜 쓰임)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;즉, Q4_K_M 란?&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span data-token-index=&quot;0&quot;&gt;Q4 비트로, K-Quant 방식, 중간 균형 타입&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;용량 &amp;darr; / 속도 &amp;uarr; / 품질 손실 최소화&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;.gguf&amp;nbsp; 파일 포맷&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;llama.cpp, koboldcpp, LM Studio 등에서 쓰는 표준 파일 포맷&lt;/li&gt;
&lt;li&gt;메타데이터 + 양자화 정보 포함&lt;/li&gt;
&lt;li&gt;요즘 로컬 LLM 표준&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style1&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;참고&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&amp;nbsp; &lt;a href=&quot;https://velog.io/@choonsik_mom/llama.cpp%EB%A1%9C-gguf-%EB%AA%A8%EB%8D%B8-%EC%84%9C%EB%B9%99%ED%95%98%EA%B8%B0-ul02hone&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;llama.cpp로 gguf 모델 서빙하기&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;figure id=&quot;og_1766563672866&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;llama.cpp로 gguf 모델 서빙하기&quot; data-og-description=&quot;뭣?! GPU 없이도 LLM 서브가 가능하다고? 이거 진짜예요?&quot; data-og-host=&quot;velog.io&quot; data-og-source-url=&quot;https://velog.io/@choonsik_mom/llama.cpp%EB%A1%9C-gguf-%EB%AA%A8%EB%8D%B8-%EC%84%9C%EB%B9%99%ED%95%98%EA%B8%B0-ul02hone&quot; data-og-url=&quot;https://velog.io/@choonsik_mom/llama.cpp로-gguf-모델-서빙하기-ul02hone&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cS9pea/hyZQz75jAW/hgJuMuN6OmoRq566Irf1X1/img.gif?width=654&amp;amp;height=368&amp;amp;face=0_0_654_368,https://scrap.kakaocdn.net/dn/bLl27P/hyZP2c0J1l/h53kxjsoMwjrct3F1QUQOK/img.gif?width=654&amp;amp;height=368&amp;amp;face=0_0_654_368,https://scrap.kakaocdn.net/dn/byDTlm/hyZPFnQfGq/p2W3ylalTNxHIdHqwDXqIk/img.png?width=768&amp;amp;height=855&amp;amp;face=0_0_768_855&quot;&gt;&lt;a href=&quot;https://velog.io/@choonsik_mom/llama.cpp%EB%A1%9C-gguf-%EB%AA%A8%EB%8D%B8-%EC%84%9C%EB%B9%99%ED%95%98%EA%B8%B0-ul02hone&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://velog.io/@choonsik_mom/llama.cpp%EB%A1%9C-gguf-%EB%AA%A8%EB%8D%B8-%EC%84%9C%EB%B9%99%ED%95%98%EA%B8%B0-ul02hone&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cS9pea/hyZQz75jAW/hgJuMuN6OmoRq566Irf1X1/img.gif?width=654&amp;amp;height=368&amp;amp;face=0_0_654_368,https://scrap.kakaocdn.net/dn/bLl27P/hyZP2c0J1l/h53kxjsoMwjrct3F1QUQOK/img.gif?width=654&amp;amp;height=368&amp;amp;face=0_0_654_368,https://scrap.kakaocdn.net/dn/byDTlm/hyZPFnQfGq/p2W3ylalTNxHIdHqwDXqIk/img.png?width=768&amp;amp;height=855&amp;amp;face=0_0_768_855');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;llama.cpp로 gguf 모델 서빙하기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;뭣?! GPU 없이도 LLM 서브가 가능하다고? 이거 진짜예요?&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;velog.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;</description>
      <category>LLM</category>
      <category>gguf</category>
      <category>llama.cpp</category>
      <category>sLLM</category>
      <category>로컬 서빙</category>
      <author>Joy Shin</author>
      <guid isPermaLink="true">https://storyofhz.tistory.com/21</guid>
      <comments>https://storyofhz.tistory.com/21#entry21comment</comments>
      <pubDate>Wed, 24 Dec 2025 17:22:44 +0900</pubDate>
    </item>
    <item>
      <title>백준 14501 | 퇴사 최대 수익</title>
      <link>https://storyofhz.tistory.com/9</link>
      <description>&lt;h1&gt;문제&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/14501&quot;&gt;백준 14501 | 퇴사&lt;/a&gt;&lt;br /&gt;✔️ 250729 | [1차] 못 풀고 넘어감&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상담원으로 일하고 있는 백준이는 퇴사를 하려고 한다.&lt;br /&gt;오늘부터 N+1일째 되는 날 퇴사를 하기 위해서, 남은 N일 동안 최대한 많은 상담을 하려고 한다.&lt;br /&gt;백준이는 비서에게 최대한 많은 상담을 잡으라고 부탁을 했고, 비서는 하루에 하나씩 서로 다른 사람의 상담을 잡아놓았다.&lt;br /&gt;각각의 상담은 상담을 완료하는데 걸리는 기간 Ti와 상담을 했을 때 받을 수 있는 금액 Pi로 이루어져 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;N = 7인 경우에 다음과 같은 상담 일정표를 보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1372&quot; data-origin-height=&quot;272&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/loTTN/btsPBHX25MH/pVWHVFpZG3ojrP9sp2GEiK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/loTTN/btsPBHX25MH/pVWHVFpZG3ojrP9sp2GEiK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/loTTN/btsPBHX25MH/pVWHVFpZG3ojrP9sp2GEiK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FloTTN%2FbtsPBHX25MH%2FpVWHVFpZG3ojrP9sp2GEiK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1372&quot; height=&quot;272&quot; data-origin-width=&quot;1372&quot; data-origin-height=&quot;272&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1일에 잡혀있는 상담은 총 3일이 걸리며, 상담했을 때 받을 수 있는 금액은 10이다. 5일에 잡혀있는 상담은 총 2일이 걸리며, 받을 수 있는 금액은 15이다.&lt;br /&gt;상담을 하는데 필요한 기간은 1일보다 클 수 있기 때문에, 모든 상담을 할 수는 없다. 예를 들어서 1일에 상담을 하게 되면, 2일, 3일에 있는 상담은 할 수 없게 된다. 2일에 있는 상담을 하게 되면, 3, 4, 5, 6일에 잡혀있는 상담은 할 수 없다.&lt;br /&gt;또한, N+1일째에는 회사에 없기 때문에, 6, 7일에 있는 상담을 할 수 없다.&lt;br /&gt;퇴사 전에 할 수 있는 상담의 최대 이익은 1일, 4일, 5일에 있는 상담을 하는 것이며, 이때의 이익은 10+20+15=45이다.&lt;br /&gt;상담을 적절히 했을 때, 백준이가 얻을 수 있는 최대 수익을 구하는 프로그램을 작성하시오.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;입력&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫째 줄에 N (1 &amp;le; N &amp;le; 15)이 주어진다.&lt;br /&gt;둘째 줄부터 N개의 줄에 Ti와 Pi가 공백으로 구분되어서 주어지며, 1일부터 N일까지 순서대로 주어진다. (1 &amp;le; Ti &amp;le; 5, 1 &amp;le; Pi &amp;le; 1,000)&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;예제 입력&lt;/h3&gt;
&lt;pre class=&quot;basic&quot;&gt;&lt;code&gt;7
3 10
5 20
1 10
1 20
2 15
4 40
2 200&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;출력&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫째 줄에 백준이가 얻을 수 있는 최대 이익을 출력한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;예제 출력&lt;/h3&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;45&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;알아갈 것&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;DP (동적계산법) 관련 풀이 방법 및 사고 방법&lt;/li&gt;
&lt;li&gt;체크할 정보
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;날짜별 상담 정보&lt;/li&gt;
&lt;li&gt;상담했을 때 끝나는 날&lt;/li&gt;
&lt;li&gt;가능한지 여부&lt;/li&gt;
&lt;li&gt;그 날 선택 가능한 수익&lt;/li&gt;
&lt;li&gt;dp 배열 값들이 어떻게 채워지는지&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;예제로 이해해보기 ( N=7인 경우)&lt;/li&gt;
&lt;/ul&gt;
&lt;table style=&quot;height: 163px;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr style=&quot;height: 23px;&quot;&gt;
&lt;th style=&quot;height: 23px;&quot;&gt;i&lt;/th&gt;
&lt;th style=&quot;height: 23px;&quot;&gt;T[i]&lt;/th&gt;
&lt;th style=&quot;height: 23px;&quot;&gt;P[i]&lt;/th&gt;
&lt;th style=&quot;height: 23px;&quot;&gt;끝나는 날 (i + T[i])&lt;/th&gt;
&lt;th style=&quot;height: 23px;&quot;&gt;상담 가능 여부&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;3&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;10&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;4&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;5&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;20&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;7&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;3&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;10&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;4&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;4&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;20&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;5&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;5&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;15&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;7&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;6&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;4&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;40&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;10&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;7&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;200&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;9&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;시각화한 DP 채우기 (거꾸로)&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;날짜 i:      7    6    5    4    3    2    1
상담 가능:   ❌   ❌   ✅   ✅   ✅   ✅   ✅
T[i]:        2    4    2    1    1    5    3
P[i]:      200   40   15   20   10   20   10

dp[i]:      0    0   15   35   45   45   45
                      ▲    ▲    ▲    ▲    ▲
                      └─ 15 + 0
                           &amp;uarr; dp[7]&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;상세 설명
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;✅ i = 6 (7일차)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;T[6] = 2 &amp;rarr; 7~8일 걸림 &amp;rarr; 퇴사일 넘음 ❌&lt;/li&gt;
&lt;li&gt;dp[6] = dp[7] = 0&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;✅ i = 5 (6일차)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;T[5] = 4 &amp;rarr; 6~9일 &amp;rarr; ❌&lt;/li&gt;
&lt;li&gt;dp[5] = dp[6] = 0&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;✅ i = 4 (5일차
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;T[4] = 2 &amp;rarr; 5~6일 ✅ 가능&lt;/li&gt;
&lt;li&gt;P = 15, 다음 날 dp[6] = 0&lt;/li&gt;
&lt;li&gt;&amp;rarr; dp[4] = max(15 + dp[6], dp[5]) = max(15, 0) = 15&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;✅ i = 3 (4일차)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;T = 1 &amp;rarr; 4일 상담 &amp;rarr; ✅&lt;/li&gt;
&lt;li&gt;P = 20, 다음날 = dp[4] = 15&lt;/li&gt;
&lt;li&gt;&amp;rarr; dp[3] = max(20 + 15, 15) = 35&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;✅ i = 2 (3일차)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;T = 1 &amp;rarr; 3일 상담 &amp;rarr; ✅&lt;/li&gt;
&lt;li&gt;P = 10, 다음날 = dp[3] = 35&lt;/li&gt;
&lt;li&gt;&amp;rarr; dp[2] = max(10 + 35, 35) = 45&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;✅ i = 1 (2일차)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;T = 5 &amp;rarr; 2~6일 상담 &amp;rarr; ✅&lt;/li&gt;
&lt;li&gt;P = 20, 다음날 = dp[6] = 0&lt;/li&gt;
&lt;li&gt;&amp;rarr; dp[1] = max(20 + 0, dp[2]=45) = 45&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;✅ i = 0 (1일차)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;T = 3 &amp;rarr; 1~3일 상담 &amp;rarr; ✅&lt;/li&gt;
&lt;li&gt;P = 10, 다음날 = dp[3] = 35&lt;/li&gt;
&lt;li&gt;&amp;rarr; dp[0] = max(10 + 35, 45) = 45&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;최종 DP 배열&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;i     0   1   2   3   4   5   6   7
dp = [45, 45, 45, 35, 15, 0, 0, 0]&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;상담 선택 경로
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;1일차 상담 (10) &amp;rarr; 4일차 상담 (20) &amp;rarr; 5일차 상담 (15)&lt;/li&gt;
&lt;li&gt;총 수익 = 45&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;핵심 시각화 요약&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;날짜:      1     2     3     4     5     6     7
T[i]:      3     5     1     1     2     4     2
P[i]:     10    20    10    20    15    40   200
가능?     ✅    ✅    ✅    ✅    ✅    ❌    ❌

dp[i]:   45    45    45    35    15     0     0

&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;마무리 요약&lt;/li&gt;
&lt;li&gt;dp[i]는 i일에 상담을 할지 말지 선택한 후, 가장 이득이 되는 쪽을 저장한 값&lt;/li&gt;
&lt;li&gt;우리는 뒤에서부터 채워야 dp[i + T[i]] 값을 사용할 수 있다고.&lt;/li&gt;
&lt;li&gt;이 문제는 결국 &lt;b&gt;&quot;할 수 있는 상담 중 최선의 조합을 고르는 것&quot;&lt;/b&gt;이에요.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;정답&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사실 아직 잘 이해가 안간다&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;vim&quot;&gt;&lt;code&gt;# 총 근무 일수 (퇴사는 N+1일째 함)
N = int(input())

# 각 날의 상담 소요 기간(T)과 수익(P)을 저장할 리스트
T = []
P = []

# 각 날마다 상담 정보를 입력받아 T와 P에 저장
for _ in range(N):
    t, p = map(int, input().split())
    T.append(t)
    P.append(p)

# dp[i]는 i번째 날부터 퇴사일까지 얻을 수 있는 최대 수익
# 퇴사일인 N일에는 수익이 무조건 0이므로, N+1 길이로 초기화
dp = [0] * (N+1) 

# i번째 날부터 거꾸로 dp값을 계싼해 나감 (뒤에서부터 계산)
for i in range(N - 1, -1, -1):
    # 만약, i일에 상담을 했을 때, 퇴사일 전(i + T[i] &amp;lt;= N)이라면 상담 가능
    if i + T[i] &amp;lt;= N:
        # 상ㄷ마을 하는 경우와 안 하는 경우 중 더 큰 값을 선택.
        # - 상담 하면, 수익은 P[i] + dp[i + T[i]] (상담 끝난 날부터의 최대 수익)
        # - 상담 안 하면 dp[i + 1] (그 다음 날부터의 최대 수익)
        dp[i] = max(P[i] + dp[i + T[i]], dp[i + 1])
    else:
        # 상담을 할 수 없다면, 그냥 다음 날부터의 수익이 현재 날의 수익이 된다.
        dp[i] = dp[i + 1]

# dp[0]에는 첫 날부터 최대 수익이 저장되어 있다.
print(dp[0])&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Python/알고리즘</category>
      <category>DP</category>
      <author>Joy Shin</author>
      <guid isPermaLink="true">https://storyofhz.tistory.com/9</guid>
      <comments>https://storyofhz.tistory.com/9#entry9comment</comments>
      <pubDate>Tue, 29 Jul 2025 14:03:44 +0900</pubDate>
    </item>
    <item>
      <title>백준 14499 | 주사위 굴리기</title>
      <link>https://storyofhz.tistory.com/8</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/14499&quot;&gt;백준 14499 | 주사위 굴리기&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;✔️ 250728 | [1차] 못 풀고 넘어감&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;문제&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;크기가 N&amp;times;M인 지도가 존재한다. 지도의 오른쪽은 동쪽, 위쪽은 북쪽이다. 이 지도의 위에 주사위가 하나 놓여져 있으며, 주사위의 전개도는 아래와 같다.&lt;br /&gt;&lt;b&gt;지도의 좌표는 (r, c)&lt;/b&gt; 로 나타내며, &lt;b&gt;r는 북쪽으로부터 떨어진 칸의 개수, c는 서쪽으로부터 떨어진 칸의 개수&lt;/b&gt;이다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;  2
4 1 3
  5
  6&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;주사위는 지도 위에 윗 면이 1이고, 동쪽을 바라보는 방향이 3인 상태&lt;/b&gt;로 놓여져 있으며, &lt;b&gt;놓여져 있는 곳의 좌표는 (x, y)&lt;/b&gt; 이다. &lt;b&gt;가장 처음에 주사위에는 모든 면에 0이 적혀져 있다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지도의 각 칸에는 정수가 하나씩 쓰여져 있다. 주사위를 굴렸을 때, &lt;b&gt;이동한 칸에 쓰여 있는 수가 0이면, 주사위의 바닥면에 쓰여 있는 수가 칸에 복사&lt;/b&gt;된다. &lt;b&gt;0이 아닌 경우에는 칸에 쓰여 있는 수가 주사위의 바닥면으로 복사되며, 칸에 쓰여 있는 수는 0&lt;/b&gt;이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주사위를 놓은 곳의 좌표와 이동시키는 명령이 주어졌을 때, &lt;b&gt;주사위가 이동했을 때 마다 상단에 쓰여 있는 값을 구하는 프로그램을 작성&lt;/b&gt;하시오.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;주사위는 지도의 바깥으로 이동시킬 수 없다&lt;/b&gt;. 만약 바깥으로 이동시키려고 하는 경우에는 해당 명령을 무시해야 하며, 출력도 하면 안 된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;입력&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫째 줄에 지도의 세로 크기 N, 가로 크기 M (1 &amp;le; N, M &amp;le; 20), 주사위를 놓은 곳의 좌표 x, y(0 &amp;le; x &amp;le; N-1, 0 &amp;le; y &amp;le; M-1), 그리고 명령의 개수 K (1 &amp;le; K &amp;le; 1,000)가 주어진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;둘째 줄부터 N개의 줄에 지도에 쓰여 있는 수가 북쪽부터 남쪽으로, 각 줄은 서쪽부터 동쪽 순서대로 주어진다. 주사위를 놓은 칸에 쓰여 있는 수는 항상 0이다. 지도의 각 칸에 쓰여 있는 수는 10 미만의 자연수 또는 0이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막 줄에는 이동하는 명령이 순서대로 주어진다. &lt;b&gt;동쪽은 1, 서쪽은 2, 북쪽은 3, 남쪽은 4&lt;/b&gt;로 주어진다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;출력&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이동할 때마다 주사위의 윗 면에 쓰여 있는 수를 출력한다. 만약 바깥으로 이동시키려고 하는 경우에는 해당 명령을 무시해야 하며, 출력도 하면 안 된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;내가 이해 안되는 부분&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;❓주사위랑 동서남북 이동 후 숫자를 맞추는 걸 어떻게 해야할지 모르겠다&lt;/li&gt;
&lt;li&gt;❓주사위를 어떻게 코드로 표현해야할지?&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;알아갈 것&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;문제에서 설정한 좌표계 정의&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;문제에서 좌표는 (r, c) 또는 (x, y)로 주어지고, 다음과 같이 정의 (기존에 생각하던 x, y 좌표와 다름)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;r (또는 x): &lt;b&gt;북쪽(위)&lt;/b&gt;에서부터 떨어진 칸의 개수 &amp;rarr; 행 번호&lt;/li&gt;
&lt;li&gt;c (또는 y): &lt;b&gt;서쪽(왼쪽)&lt;/b&gt;에서부터 떨어진 칸의 개수 &amp;rarr; 열 번호&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;table data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;좌표&lt;/th&gt;
&lt;th&gt;의미&lt;/th&gt;
&lt;th&gt;방향&lt;/th&gt;
&lt;th&gt;배열에서 해당 위치&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;x&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;행 번호&lt;/td&gt;
&lt;td&gt;&lt;b&gt;상하&lt;/b&gt; (북-남)&lt;/td&gt;
&lt;td&gt;배열의 행 (세로 방향)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;y&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;열 번호&lt;/td&gt;
&lt;td&gt;&lt;b&gt;좌우&lt;/b&gt; (서-동)&lt;/td&gt;
&lt;td&gt;배열의 열 (가로 방향)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;pre class=&quot;markdown&quot;&gt;&lt;code&gt;0행 &amp;rarr; 북쪽
&amp;darr;
1행
&amp;darr;
2행 &amp;rarr; 남쪽

서쪽   &amp;rarr;   동쪽
[0][0] [0][1] [0][2] [0][3]
[1][0] [1][1] [1][2] [1][3]
[2][0] [2][1] [2][2] [2][3]&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;여기서 (x, y) = (1, 2) 라면 &amp;rarr; 1행 2열 &amp;rarr; board[1][2]&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;# 명령: 동(1), 서(2), 북(3), 남(4)
dx = [0, 0, -1, 1]  # 행 변화: 동서북남
dy = [1, -1, 0, 0]  # 열 변화: 동서북남&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;동쪽으로 이동 (&amp;rarr;): 행 변화 없음, 열 +1 &amp;rarr; dx=0, dy=1&lt;/li&gt;
&lt;li&gt;서쪽으로 이동 (&amp;larr;): 행 변화 없음, 열 -1 &amp;rarr; dx=0, dy=-1&lt;/li&gt;
&lt;li&gt;북쪽으로 이동 (&amp;uarr;): 행 -1, 열 변화 없음 &amp;rarr; dx=-1, dy=0&lt;/li&gt;
&lt;li&gt;남쪽으로 이동 (&amp;darr;): 행 +1, 열 변화 없음 &amp;rarr; dx=1, dy=0&lt;/li&gt;
&lt;/ul&gt;
&lt;table data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;개념&lt;/th&gt;
&lt;th&gt;문제의 &lt;code&gt;x&lt;/code&gt; 또는 &lt;code&gt;r&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;문제의 &lt;code&gt;y&lt;/code&gt; 또는 &lt;code&gt;c&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;실제 의미&lt;/td&gt;
&lt;td&gt;행 (위아래, 북-남)&lt;/td&gt;
&lt;td&gt;열 (좌우, 서-동)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;이동 방향&lt;/td&gt;
&lt;td&gt;상하 방향&lt;/td&gt;
&lt;td&gt;좌우 방향&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;배열 인덱스&lt;/td&gt;
&lt;td&gt;&lt;code&gt;board[x][y]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;board[행][열]&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;주사위를 배열로 표현하기&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;주사위 평면 전개도 (1이 윗면)&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;    2
4   1   3
    5
    6&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;위 전개도를 1이 위로 오게 해서 주사위 모양으로 접어보면, 아래와 같이 볼 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;table data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;인덱스&lt;/th&gt;
&lt;th&gt;의미&lt;/th&gt;
&lt;th&gt;전개도 숫자&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;윗면&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;바닥면&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;앞면&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;뒷면&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;왼쪽면&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;오른쪽면&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;첫 번째로 주사위를 배열로 아래와 같이 정의한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;gcode&quot;&gt;&lt;code&gt;dice = [0] * 6

# dice\[0\] = 윗면 (top)
# dice\[1\] = 아랫면 (bottom)
# dice\[2\] = 앞면 (남쪽, front)
# dice\[3\] = 뒷면 (북쪽, back)
# dice\[4\] = 왼쪽면 (서쪽, left)
# dice\[5\] = 오른쪽면 (동쪽, right)&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;주사위를 굴렸을 때, 알맞게 면을 바꿔준다
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;면의 위치만 바뀌는 거지, 각 면의 값은 변하지 않음을 기억하기.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;taggerscript&quot;&gt;&lt;code&gt;# 동쪽으로 굴릴 때

top, bottom, left, right = dice\[0\], dice\[1\], dice\[4\], dice\[5\]  
dice\[0\] = left # 왼쪽면의 숫자 &amp;rarr; 윗면  
dice\[1\] = right # 오른쪽면의 숫자 &amp;rarr; 바닥면  
dice\[4\] = bottom # 바닥면의 숫자 &amp;rarr; 왼쪽면  
dice\[5\] = top # 윗면의 숫자 &amp;rarr; 오른쪽면&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;정의한 방향과 주사위 배열을 사용해서 주사위를 던지는 함수(&lt;code&gt;roll&lt;/code&gt;) 만들기&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;direction 0, 1, 2, 3 이 어느 방향을 가리키는지는 문제에 정의되어 있음.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;mipsasm&quot;&gt;&lt;code&gt;def roll(dice, direction):
    top, bottom, front, back, left, right = dice

    if direction == 0:  # 동쪽
        dice[0], dice[1], dice[4], dice[5] = left, right, bottom, top
    elif direction == 1:  # 서쪽
        dice[0], dice[1], dice[4], dice[5] = right, left, top, bottom
    elif direction == 2:  # 북쪽
        dice[0], dice[1], dice[2], dice[3] = front, back, bottom, top
    elif direction == 3:  # 남쪽
        dice[0], dice[1], dice[2], dice[3] = back, front, top, bottom
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;정답&lt;/h3&gt;
&lt;pre class=&quot;mipsasm&quot;&gt;&lt;code&gt;
n, m, x, y, k = map(int, input().split())
board = [list(map(int, input().split())) for _ in range(n)]
commands = list(map(int, input().split()))

dice = [0] * 6  # [top, bottom, front, back, left, right]

# 동서북남
dx = [0, 0, -1, 1]
dy = [1, -1, 0, 0]

# 주사위 굴리기 함수 정의
def roll(direction):
    top, bottom, front, back, left, right = dice
    if direction == 0:  # 동
        dice[0], dice[1], dice[4], dice[5] = left, right, bottom, top
        # e.g. left의 값이 dice[0](=위)로 가게 된다
        # e.g. right의 값이 dice[1](=아래)로 가게 된다
    elif direction == 1:  # 서
        dice[0], dice[1], dice[4], dice[5] = right, left, top, bottom
    elif direction == 2:  # 북
        dice[0], dice[1], dice[2], dice[3] = front, back, bottom, top
    elif direction == 3:  # 남
        dice[0], dice[1], dice[2], dice[3] = back, front, top, bottom

# 주사위 굴리기 명령 수행
for command in commands:
    dir = command - 1
    nx, ny = x + dx[dir], y + dy[dir]

    if not (0 &amp;lt;= nx &amp;lt; n and 0 &amp;lt;= ny &amp;lt; m):
        continue  # 범위 밖이면 무시 (지도 밖으로 나가지 않도록)

    roll(dir)  # 주사위 굴리기

    # 주사위와 지도 간 상호 규칙 적용
    if board[nx][ny] == 0:
        board[nx][ny] = dice[1]  # 바닥면 복사
    else:
        dice[1] = board[nx][ny]  # 칸의 숫자 -&amp;gt; 주사위
        board[nx][ny] = 0  # 복사된 칸은 0으로 초기화

    print(dice[0])  # 윗면 출력
    x, y = nx, ny
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Python/알고리즘</category>
      <category>주사위</category>
      <author>Joy Shin</author>
      <guid isPermaLink="true">https://storyofhz.tistory.com/8</guid>
      <comments>https://storyofhz.tistory.com/8#entry8comment</comments>
      <pubDate>Mon, 28 Jul 2025 17:56:57 +0900</pubDate>
    </item>
    <item>
      <title>빠른 입력을 위한 input vs. sys.stdin.readline</title>
      <link>https://storyofhz.tistory.com/7</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;본격적으로 for문 문제를 풀기 전에 주의해야 할 점이 있다.&lt;br /&gt;입출력 방식이 느리면 여러 줄을 입력받거나 출력할 때 시간초과가 날 수 있다는 점이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 위해, &lt;code&gt;Python&lt;/code&gt;을 사용하고 있다면, &lt;code&gt;input&lt;/code&gt; 대신 &lt;code&gt;sys.stdin.readline&lt;/code&gt;을 사용할 수 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;input vs. sys.stdin.readline&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;차이점 1&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;sys.stdin.readline&lt;/code&gt;을 사용하는 경우, 맨 끝의 개행문자까지 같이 입력받기 때문에 문자열을 저장하고 싶을 경우, &lt;code&gt;.rstrip()&lt;/code&gt;을 추가로 해 주는 것이 좋다.&lt;/p&gt;
&lt;pre class=&quot;makefile&quot;&gt;&lt;code&gt;# 입력으로 abc를 치고 엔터를 친 경우,

s = input()  # 이 때, s는 &quot;abc&quot; 
len(s)  # 3

s = sys.stdin.readline()  #이 때, s는 &quot;abc\n&quot;
len(s)  # 4&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;lua&quot;&gt;&lt;code&gt;# sys.stdin.readline()를 쓸 때, rstrip()으로 개행문자 버리기

input = lambda: sys.stdin.readline().rstrip()  # 이러면, abc를 input으로 받아도, input에는 'abc\n'가 아니라 'abc'가 저장된다.&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1753340937640&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# Example

# input 사용할 때
t = 5
inp = [map(int, input().split()) for _ in range(t)]
for _inp in inp:
	print(sum(_inp))
    

# sys 사용할 때 _ 개행 문자 처리 등 번거롭지만, 입력량 많을 때 훨씬 빠름
import sys

t = 5
inp = [list(map(int, sys.stdin.readline().rstrip().split())) for _ in range(t)]
for _inp in inp:
	print(sum(_inp))&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;차이점 2&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;input&lt;/code&gt;은 입력의 끝 (EOF)을 만났을 때 EOFError를 raise하지만&lt;br /&gt;&lt;code&gt;sys.stdin.readline&lt;/code&gt;은 에러를 발생시키지 않고 정상적으로 빈 문자열을 반환한다.&lt;br /&gt;따라서, &lt;code&gt;input&lt;/code&gt; 에서 사용하는 예외처리를 &lt;code&gt;sys.stdin.readline&lt;/code&gt;에서 사용하면 에러가 발생할 수 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;참고&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://djm03178.tistory.com/21&quot;&gt;https://djm03178.tistory.com/21&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/15552&quot;&gt;백준 | 빠른 A+B&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Python/알고리즘</category>
      <category>input</category>
      <category>sys.stdin.readline()</category>
      <author>Joy Shin</author>
      <guid isPermaLink="true">https://storyofhz.tistory.com/7</guid>
      <comments>https://storyofhz.tistory.com/7#entry7comment</comments>
      <pubDate>Thu, 24 Jul 2025 15:47:00 +0900</pubDate>
    </item>
    <item>
      <title>print(end=&amp;quot; &amp;quot;) 사용해서 한 줄에 출력하기</title>
      <link>https://storyofhz.tistory.com/4</link>
      <description>&lt;pre id=&quot;code_1751623034629&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;A, B = map(int, input().split())
print(int(A &amp;lt; B), int(int(A == B)))&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 처럼 쓸 수도 있지만&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래처럼 쓸 수도 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;print()의 `end=&quot; &quot;` 인자 사용하기&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1751623104325&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 변수 선언, 입력
inp = input()
arr = inp.split()
a = int(arr[0])
b = int(arr[1])

# 출력
if a &amp;lt; b:
	print(&quot;1&quot;, end=&quot; &quot;)
else:
	print(&quot;0&quot;, end=&quot; &quot;)

if a == b:
	print(&quot;1&quot;)
else:
	print(&quot;0&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 쓰면, `if a &amp;lt; b` 결과 뒤에 `a==b` 의 결과를 붙여서 출력할 수 있다.&lt;/p&gt;</description>
      <category>Python/알고리즘</category>
      <category>if문</category>
      <category>코드트리</category>
      <author>Joy Shin</author>
      <guid isPermaLink="true">https://storyofhz.tistory.com/4</guid>
      <comments>https://storyofhz.tistory.com/4#entry4comment</comments>
      <pubDate>Fri, 4 Jul 2025 18:59:43 +0900</pubDate>
    </item>
    <item>
      <title>True, False를 쉽게 1과 0으로 프린트하기</title>
      <link>https://storyofhz.tistory.com/3</link>
      <description>&lt;pre id=&quot;code_1751622272918&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;A, B = map(int, input().split())

if A &amp;gt;= B:
    print(1)
else:
    print(0)
if A &amp;gt; B:
    print(1)
else:
    print(0)
if B &amp;gt;= A:
    print(1)
else: 
    print(0)
if B &amp;gt; A:
    print(1)
else:
    print(0)
if A == B:
    print(1)
else:
    print(0)
if A != B:
    print(1)
else: 
    print(0)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;손 아프게 다 쓰고 있었는데..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 코드를 아래처럼 간단하게 쓸 수 있었다&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;(!) `True`, `False` 를 1과 0으로 print하고 싶으면, 그냥 `int` 처리를 하면 된다&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1751622319232&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 변수 선언, 입력
inp = input()
arr = inp.split()
a = int(arr[0])
b = int(arr[1])

# 출력
print(int(a &amp;gt;= b))
print(int(a &amp;gt; b))
print(int(a &amp;lt;= b))
print(int(a &amp;lt; b))
print(int(a == b))
print(int(a != b))&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Python/알고리즘</category>
      <category>if문</category>
      <category>코드트리</category>
      <author>Joy Shin</author>
      <guid isPermaLink="true">https://storyofhz.tistory.com/3</guid>
      <comments>https://storyofhz.tistory.com/3#entry3comment</comments>
      <pubDate>Fri, 4 Jul 2025 18:46:41 +0900</pubDate>
    </item>
    <item>
      <title>느린 LLM을 위한 해법, Speculative Decoding</title>
      <link>https://storyofhz.tistory.com/2</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;요즘 챗GPT 같은 대규모 언어 모델(LLM)의 능력을 보면 똑똑하다는 생각이 들다가도, 가끔 답변이 너무 느리게 나와 답답할 때가 많다. LLM, 성능은 좋은데 왜 이렇게 느린 걸까? 이 속도로는 실시간 서비스는 어림도 없겠다는 생각이 든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 고민을 해결해 줄 기술이 바로 &lt;b&gt;Speculative Decoding&lt;/b&gt;이다. 큰 LLM의 성능은 그대로 유지하면서 추론 속도만 빠르게 만드는 방법이다. 심지어 모델을 추가로 학습시킬 필요도 없다. 이번 포스팅에서는 &lt;b&gt;Speculative Decoding&lt;/b&gt;이 무엇인지, 어떤 원리로 속도 문제를 해결하는지 찾아본 내용들을 정리해보려 한다.&amp;nbsp;&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;2&quot; data-ke-size=&quot;size23&quot;&gt;문제점 1: LLM은 왜 느릴까? - 한 글자 한 글자 생성하는 구조&lt;/h3&gt;
&lt;p data-path-to-node=&quot;3&quot; data-ke-size=&quot;size16&quot;&gt;LLM이 텍스트를 생성하는 방식을 보면 그 이유를 알 수 있다. 대부분의 LLM은 &lt;b&gt;AutoRegressive(자기회귀적)&lt;/b&gt; Transformer라는 구조를 기반으로 한다. AutoRegressive는 쉽게 말해 &quot;이전 단어들을 보고 다음 단어를 예측&quot;하는 방식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;kakaotv&quot; data-video-url=&quot;https://tv.kakao.com/v/455463947&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/0VYPC/hyY0kLgLDg/cOJQ0i2tKkjZu0KZxUKSvK/img.jpg?width=1920&amp;amp;height=938&amp;amp;face=0_0_1920_938,https://scrap.kakaocdn.net/dn/btSZJu/hyYYBgzo4Q/jEqL1WP0Qjld9wn3PCG4V1/img.jpg?width=1920&amp;amp;height=938&amp;amp;face=0_0_1920_938&quot; data-video-width=&quot;430&quot; data-video-height=&quot;210&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;420&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-play-service=&quot;daum_tistory&quot; data-original-url=&quot;&quot; data-video-title=&quot;&quot;&gt;&lt;iframe src=&quot;https://play-tv.kakao.com/embed/player/cliplink/455463947?service=daum_tistory&quot; width=&quot;430&quot; height=&quot;210&quot; frameborder=&quot;0&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption&gt;AutoRegressive Decoding&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;The quick brown fox jumps&quot; 라는 문장이 있다면,&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&quot;The&quot; 다음에 &quot;quick&quot;을 예측하고,&lt;/li&gt;
&lt;li&gt;&quot;The quick&quot; 다음에 &quot;brown&quot;을 예측하고,&lt;/li&gt;
&lt;li&gt;&quot;The quick brown&quot; 다음에 &quot;fox&quot;를 예측하고...&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 식으로 한 토큰(단어 또는 글자)씩 순차적으로 생성한다. 이 때문에 &lt;u&gt;&lt;b&gt;병렬 처리가 어려워 추론 속도가 느려지는 근본적인 원인&lt;/b&gt;&lt;/u&gt;이 된다.&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;5&quot; data-ke-size=&quot;size23&quot;&gt;문제점 2: LLM은 왜 느릴까? - 쉬운 건데 굳이?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LLM이 생성하는 모든 토큰이 다 어려운 건 아니다. 어떤 토큰은 쉽게 예측할 수 있고, 어떤 토큰은 좀 더 깊은 고민이 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어,&lt;br /&gt;&quot;What is the square root of 7? The square root of 7 is &lt;b&gt;2.646&lt;/b&gt;&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 &quot;The square root of 7 is&quot; 다음에 오는 &quot;2.646&quot;을 예측하는 건 난이도가 좀 있다. 앞 문맥에서 반복되는 패턴도 없고, 실제로 계산을 해야 하기 때문. 이런 건 거대한 LLM이 실력 발휘를 해야 한다. (난이도: 상)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, &quot;The square root of 7&quot;과 같이 문맥에서 &lt;b&gt;반복되는 패턴&lt;/b&gt;이 있는 부분은 어떨까? 굳이 똑똑한 거대 LLM이 아니더라도, 좀 더 가벼운 모델도 충분히 잘 맞출 수 있다. (난이도: 하)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 쉬운 토큰까지 거대 LLM이 하나하나 다 처리하려니, 비효율이 발생하는 것.&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;9&quot; data-ke-size=&quot;size23&quot;&gt;문제점 3: LLM은 왜 느릴까? - 메모리 대역폭 병목 현상&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LLM의 추론 속도를 제한하는 또 다른 주범은 바로 &lt;b&gt;메모리 대역폭 병목 현상 (Memory Bandwidth Bottleneck)&lt;/b&gt; 이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;메모리 대역폭이란?&lt;/b&gt; 초당 VRAM에서 GPU로 전송할 수 있는 데이터의 양을 의미한다.&lt;/li&gt;
&lt;li&gt;LLM의 거대한 가중치(모델 파라미터)는 VRAM에 저장된다.&lt;/li&gt;
&lt;li&gt;LLM이 한 토큰을 생성할 때마다, 이 &lt;b&gt;가중치 전체를 VRAM에서 GPU로 읽어와야&lt;/b&gt; 한다. 모델 크기가 1TB라면, 매 토큰 생성 시 1TB의 데이터를 읽는 셈. (로딩과는 다른 개념. 이미 로딩된 데이터를 계속 읽어오는 과정.)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;10&quot; data-ke-size=&quot;size16&quot;&gt;GPU의 연산 능력은 엄청나지만, Transformer 추론 시에는 1바이트당 고작 10번 정도의 연산만 수행한다. 즉, GPU는 데이터를 더 달라고 하지만 메모리에서 데이터를 가져오는 속도가 느려서 GPU가 놀게 되는 현상이 발생한다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;10&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;11&quot; data-ke-size=&quot;size16&quot;&gt;결국 LLM 추론이 느린 건 GPU 연산 능력 부족이 아니라, 메모리에서 가중치를 읽어오는 속도 때문인 경우가 많다. 메모리 문제만 해결하면 병렬 처리로 속도를 올릴 수 있다는 뜻이다. Speculative Decoding은 바로 이 지점을 파고든다.&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;12&quot; data-ke-size=&quot;size23&quot;&gt;Speculative Decoding이란 무엇인가?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Speculative Decoding은 마치 &quot;&lt;b&gt;똑똑하지만 조금 느린 교수님(큰 모델)&lt;/b&gt;&quot;과 &quot;&lt;b&gt;엄청 빠르지만 가끔 실수하는 조교(작은 모델)&lt;/b&gt;&quot;가 &lt;b&gt;함께 일하는 방식&lt;/b&gt;과 같다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;14,0,0&quot;&gt;조교(작은 모델)의 활약:&lt;/b&gt; 가볍고 빠른 작은 모델(근사 모델, Mq)이 먼저 다음 내용을 추측하여 여러 개의 후보 토큰(예: 5개)을 빠르게 생성한다. (AutoRegressive 방식이지만 모델이 작아서 매우 빠름)&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;14,1,0&quot;&gt;교수님(큰 모델)의 검토:&lt;/b&gt; 똑똑한 큰 모델(타겟 모델, Mp)은 조교가 가져온 여러 개의 후보 토큰들을 한 번에 병렬적으로 검토한다. &amp;nbsp;&quot;음, 첫 번째, 두 번째, 세 번째는 맞는데, 네 번째는 틀렸군!&quot;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;결과 반영:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;조교의 예측이 맞은 부분까지는 그대로 사용. (시간 절약)&lt;/li&gt;
&lt;li&gt;틀린 부분부터는 교수님이 직접 &quot;이게 정답이다.&quot; 하고 올바른 토큰을 생성.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 어떤 장점이 있을까?&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;큰 모델 호출 횟수 대폭 감소:&lt;/b&gt; 예전에는 매 토큰마다 큰 모델을 불러야 했지만, 이제는 여러 토큰을 한 번에 처리하니 메모리 부담이 줄어든다. (메모리 대역폭 병목 완화)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;GPU 활용도가 높아진다:&lt;/b&gt; 큰 모델이 한 번 호출될 때 여러 토큰을 병렬로 검증하면서 더 많은 연산을 수행하게 된다. 즉, 메모리 읽기 시간 대비 연산량이 늘어나 GPU가 더 효율적으로 일하게 된다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;작은 모델은 원래 빠름:&lt;/b&gt; 작은 모델은 가중치 크기도 작고 계산도 빨라서 부담이 훨씬 적다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결론적으로, &lt;b&gt;큰 모델의 정확도는 유지하면서, 작은 모델의 속도를 활용해 전체 추론 시간을 단축&lt;/b&gt;하는 것이 Speculative Decoding의 핵심!&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;16&quot; data-ke-size=&quot;size23&quot;&gt;Speculative Decoding 상세 과정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;용어를 정리하자면, 다음과 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;큰 모델 (Mp, 타겟 모델):&lt;/b&gt; 우리가 원하는 고품질의 결과를 내는, 하지만 느린 메인 모델.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;작은 모델 (Mq, 근사 모델):&lt;/b&gt; 크기가 작고 빨라서 추측을 담당하는 보조 모델.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;감마 (&amp;gamma;):&lt;/b&gt; 작은 모델(Mq)이 한 번에 생성하는 예측 토큰의 개수.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;19&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;19&quot;&gt;프로세스 예시&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;349&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rCRUB/btsOhFlHszj/UIsDonygpSifizHw0EjURk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rCRUB/btsOhFlHszj/UIsDonygpSifizHw0EjURk/img.png&quot; data-alt=&quot;Speculative Decoding Process (Google, 2022.11)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rCRUB/btsOhFlHszj/UIsDonygpSifizHw0EjURk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrCRUB%2FbtsOhFlHszj%2FUIsDonygpSifizHw0EjURk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;349&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;349&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Speculative Decoding Process (Google, 2022.11)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;시작!&lt;/b&gt; &quot;[START]&quot; 토큰을 입력으로 받는다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;작은 모델(Mq)의 제안:&lt;/b&gt; 작은 모델이 &amp;gamma;=5라고 가정하고, &quot;japan&amp;rsquo;s benchmark bond&quot;까지 5개의 토큰을 빠르게 생성한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;[START] japan&amp;rsquo;s benchmark bond&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;큰 모델(Mp)의 병렬 검증:&lt;/b&gt; 큰 모델은 작은 모델이 생성한 각 토큰이 맞는지 병렬적으로 확인한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;P([START] &amp;rarr; japan)&lt;/code&gt; ?&lt;/li&gt;
&lt;li&gt;&lt;code&gt;P([START] japan &amp;rarr; &amp;rsquo;s)&lt;/code&gt; ?&lt;/li&gt;
&lt;li&gt;&lt;code&gt;P([START] japan&amp;rsquo;s &amp;rarr; benchmark)&lt;/code&gt; ?&lt;/li&gt;
&lt;li&gt;&lt;code&gt;P([START] japan&amp;rsquo;s benchmark &amp;rarr; bond)&lt;/code&gt; ?&lt;/li&gt;
&lt;li&gt;&lt;code&gt;P([START] japan&amp;rsquo;s benchmark bond &amp;rarr; yields)&lt;/code&gt; ? (마지막은 다음 토큰 예측)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;승인(Accept) / 거부(Reject):&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;만약 작은 모델이 제안한 &quot;japan&amp;rsquo;s benchmark bond&quot;까지 모두 정답이라면 (초록색 ✅), 큰 모델은 이 4개 토큰을 모두 승인하고, 자신이 예측한 다음 토큰 &quot;yields&quot;까지 추가하여 총 5개의 토큰을 한 번의 호출로 얻게 된다.&lt;/li&gt;
&lt;li&gt;만약 중간에 틀린 토큰이 있다면 (빨간색 ❌), 그 지점부터는 작은 모델의 제안을 거부한다. 예를 들어 &quot;benchmark&quot;까지는 맞았지만 &quot;bond&quot;가 틀렸다면, &quot;japan&amp;rsquo;s benchmark&quot;까지 3개는 승인하고, 틀린 &quot;bond&quot; 대신 큰 모델이 직접 올바른 토큰(파란색  )을 생성하기 시작한다. 이 경우, 거부된 토큰 이후의 작은 모델 제안은 모두 버려진다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;여기서 중요한 포인트!&lt;/b&gt; 큰 모델은 이미 어떤 토큰들을 검증해야 할지 작은 모델로부터 전달받았기 때문에, 각 토큰에 대한 확률 계산을 &lt;b&gt;병렬적으로 동시에 처리&lt;/b&gt;할 수 있다는 점. 이것이 바로 속도 향상의 핵심이다.&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;22&quot; data-ke-size=&quot;size23&quot;&gt;확률 분포 보정 (p'(x))&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작은 모델의 예측이 거부되었을 때, 큰 모델은 자신의 원래 확률 분포를 그대로 사용하지 않고 수정된 확률 분포 p'(x)를 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 작은 모델의 예측 실패가 전체 결과 품질을 해치지 않도록 방지하고, 큰 모델의 원래 분포를 최대한 유지하기 위함이다. &quot;작은 모델이 실수한 부분을 감안해서 최종 답을 내라&quot;는 의미다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;작은 모델(Mq)의 예측이 &amp;gamma;개만큼 모두 승인되면 (n=&amp;gamma;):&lt;/b&gt; 큰 모델(Mp)이 원래 예측했을 다음 토큰의 확률 분포 &lt;code&gt;p'(x) = p_n+1(x)&lt;/code&gt;를 그대로 사용합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;작은 모델(Mq)의 예측이 일부만 승인되면 (n&amp;lt;&amp;gamma;):&lt;/b&gt; 큰 모델(Mp)의 (n+1)번째 토큰 확률 분포에서 작은 모델(Mq)의 (n+1)번째 토큰 확률 분포를 보정한 값 &lt;code&gt;p'(x) = norm(max(0, p_n+1(x) - q_n+1(x)))&lt;/code&gt;을 사용합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;그래서, 실제 성능은?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Google 연구에 따르면, Speculative Decoding을 사용했을 때 실제 경과 시간(Wall time)이 눈에 띄게 줄어들었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;244&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uyTye/btsOh3mhkJs/ysZehuAk8x9cqQKSC1yai1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uyTye/btsOh3mhkJs/ysZehuAk8x9cqQKSC1yai1/img.png&quot; data-alt=&quot;Fast Inference from Transformers via Speculative Decoding, Google, 2022.11)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uyTye/btsOh3mhkJs/ysZehuAk8x9cqQKSC1yai1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuyTye%2FbtsOh3mhkJs%2FysZehuAk8x9cqQKSC1yai1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;244&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;244&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Fast Inference from Transformers via Speculative Decoding, Google, 2022.11)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1a1c1e; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;위 그래프에서 가장 아래에 있는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;&lt;span&gt;&quot;Base&quot; 막대&lt;/span&gt;&lt;/b&gt;&lt;span&gt;가 일반적인 순차적 디코딩(AutoRegressive Decoding) 방식을 나타낸다. 초기 노란색 부분(M_p 인코더) 이후, 각 토큰이 생성될 때마다 상대적으로 긴 진한 보라색 블록(M_p 디코더)이 순차적으로 이어져 전체적인 Wall time이 길게 나타난다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1a1c1e; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;반면, 위 두 개의 &lt;b&gt;Speculative Decoding 막대(&quot;&amp;gamma;=3&quot;, &quot;&amp;gamma;=7&quot;)&lt;/b&gt;를 보자.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #1a1c1e; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;초기 인코더 부분(노란색 M_p 인코더와 짧은 주황색 M_q 인코더) 이후,&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;span&gt;짧은 하늘색 블록들(M_q 디코더)&lt;/span&gt;&lt;/b&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;이 먼저 나타난다. 이것이 작은 모델(M_q)이 &amp;gamma;개의 후보 토큰을 빠르게 생성하는 부분이다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;그 다음,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;&lt;span&gt;상대적으로 큰 진한 보라색 블록(M_p 디코더)&lt;/span&gt;&lt;/b&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;이 한 번 나타나는데, 이것이 큰 모델(M_p)이 작은 모델이 제안한 &amp;gamma;개의 토큰을 한꺼번에 병렬적으로 검증하는 부분이다.&amp;nbsp;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1a1c1e; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;이 (하늘색 M_q 디코더 + 진한 보라색 M_p 디코더) 패턴이 반복되면서, &quot;Base&quot; 막대보다 전체 Wall time이 훨씬 짧아진 것을 명확히 볼 수 있다. 특히 &amp;gamma; 값이 클수록(예: &amp;gamma;=7일 때) 한 번의 큰 모델 호출로 더 많은 토큰을 처리할 수 있어 속도 향상 효과가 더 커지게 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1a1c1e; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;일반적으로&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;&lt;span&gt;2배에서 3배까지의 속도 향상&lt;/span&gt;&lt;/b&gt;&lt;span&gt;을 기대할 수 있다고 한다. (출처: Google AI Blog)&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Speculative Decoding, 핵심 요약&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;27,0,0&quot;&gt;문제:&lt;/b&gt; LLM은 한 글자씩 생성하는 방식과 메모리 병목 현상 때문에 느리다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;27,1,0&quot;&gt;해결책:&lt;/b&gt; 가볍고 빠른 작은 모델이 먼저 예측하고, 큰 모델이 병렬로 검증한다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;27,2,0&quot;&gt;효과:&lt;/b&gt; 큰 모델 호출 횟수 감소, GPU 효율 증가, 모델 추가 학습 없이 속도 향상, 품질 유지&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Speculative Decoding은 LLM의 느린 추론 속도를 해결하는 효율적인 방법이다. 앞으로 더 빠르고 쾌적한 LLM 서비스를 가능하게 할 기술임이 분명하므로, 알아둘 필요가 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;참고 자료:&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Leviathan, Y., Meng, M., Fitch, J., Polyak, A., Baevski, A., Adi, Y., &amp;amp; Chen, Y. (2022). Fast Inference from Transformers via Speculative Decoding. &lt;i&gt;arXiv preprint arXiv:2211.17192&lt;/i&gt;. (&lt;a href=&quot;https://arxiv.org/abs/2211.17192&quot;&gt;https://arxiv.org/abs/2211.17192&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Kim, C., Lee, S., Lee, G., Kim, S., &amp;amp; Kim, J. (2023). Accelerating Large Language Model Decoding with Speculative Sampling. &lt;i&gt;arXiv preprint arXiv:2302.01318&lt;/i&gt;. (DeepMind, 이 논문은 &quot;Speculative Sampling&quot;으로 약간 다른 변형을 제안하지만, 기본 아이디어는 유사합니다.) (&lt;a href=&quot;https://arxiv.org/abs/2302.01318&quot;&gt;https://arxiv.org/abs/2302.01318&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Google Research Blog. (2023). Looking back at Speculative Decoding. (&lt;a href=&quot;https://research.google/blog/looking-back-at-speculative-decoding/&quot;&gt;https://research.google/blog/looking-back-at-speculative-decoding/&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Hugging Face Blog. (2023). Assisted Generation: a new way to speed up inference for large language models. (&lt;a href=&quot;https://huggingface.co/blog/assisted-generation&quot;&gt;https://huggingface.co/blog/assisted-generation&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Noting11 Tistory Blog. Speculative Decoding. (&lt;a href=&quot;https://noting11.tistory.com/6&quot;&gt;https://noting11.tistory.com/6&lt;/a&gt;)&lt;/li&gt;
&lt;/ol&gt;</description>
      <category>LLM</category>
      <category>Inference</category>
      <category>LLM</category>
      <category>speculative decoding</category>
      <author>Joy Shin</author>
      <guid isPermaLink="true">https://storyofhz.tistory.com/2</guid>
      <comments>https://storyofhz.tistory.com/2#entry2comment</comments>
      <pubDate>Wed, 28 May 2025 17:53:28 +0900</pubDate>
    </item>
    <item>
      <title>데이터 로드만 몇 시간째 기다리는 당신에게 필요한 IterableDataset!</title>
      <link>https://storyofhz.tistory.com/1</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Python, 거대한 데이터셋 앞에서 막막할 때 &amp;mdash; IterableDataset&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;학습용 데이터셋이 너무 커서 OOM(Out Of Memory) 오류로 밤새 돌린 코드가 멈춰버린 경험, 한 번쯤 있을 거다. 특히 오디오나 이미지처럼 개별 파일 용량이 큰 데이터를 다루다 보면 이런 일이 자주 생긴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는 그 메모리 문제를 꽤 깔끔하게 해결해주는 Hugging Face datasets 라이브러리의 streaming=True 옵션, 즉 IterableDataset 사용법을 오디오 처리 예제를 통해 정리해봤다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;기존 방식의 문제&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보통 데이터를 불러올 때는 이런 식으로 한다.&lt;/p&gt;
&lt;pre class=&quot;clean&quot;&gt;&lt;code&gt;# dataset = load_dataset(&quot;csv&quot;, data_files=&quot;my_large_data.csv&quot;)
# --&amp;gt; 이 순간, CSV 전체가 메모리로 올라오려고 시도
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파일이 수십 GB라면?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;거기에 오디오 파일들까지 전처리해서 input_features로 변환해야 한다면? 램은 순식간에 바닥나고, 프로그램은 멈춘다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;IterableDataset: 데이터를 하나씩 꺼내 쓰는 방식&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;load_dataset에 streaming=True를 넘기면 동작 방식이 달라진다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;my_iterable_dataset = load_dataset(&quot;csv&quot;,
                                   data_files={&quot;valid&quot;: &quot;valid_under_10.csv&quot;},
                                   streaming=True,
                                   features=features)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반 Dataset은 뷔페처럼 처음부터 모든 걸 차려놓고 시작한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 IterableDataset은 컨베이어 벨트 스시처럼, 접시 하나가 오면 먹고, 그 다음 접시를 기다리는 방식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 데이터를 한 번에 올리는 대신, 필요할 때마다 파일에서 한 줄씩 읽어온다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;실제 코드&lt;/b&gt;&lt;/h4&gt;
&lt;pre class=&quot;python&quot;&gt;&lt;code&gt;import re
import json
import toml
import sys
from functools import partial

from datasets import Features, Value, load_dataset, Audio
from transformers import Wav2Vec2CTCTokenizer, SeamlessM4TFeatureExtractor, Wav2Vec2BertProcessor

def remove_special_characters(batch):
    batch[&quot;sentence&quot;] = re.sub(r'\(.*?\)', '', batch[&quot;sentence&quot;])
    batch[&quot;sentence&quot;] = re.sub(r'[^a-zA-Z0-9가-힣\s]', '', batch[&quot;sentence&quot;]).strip()
    return batch

def audio_preprocess(batch):
    audio_loader = Audio(sampling_rate=16_000)
    audio_data = audio_loader.decode_example(audio_loader.encode_example(batch['audio']))
    batch['audio'] = audio_data
    return batch

def input_process(batch, processor, make_label_fn):
    audio = batch['audio']
    batch['input_features'] = processor(audio['array'], sampling_rate=audio['sampling_rate']).input_features[0]
    batch['input_length'] = len(batch['input_features'])
    batch['labels'] = processor.tokenizer(text=batch['sentence']).input_ids
    return batch

if __name__ == &quot;__main__&quot;:
    def placeholder_make_label(sentence):
        return list(sentence)

    process_fn = partial(input_process, processor=processor, make_label_fn=placeholder_make_label)

    data_files = {&quot;valid&quot;: &quot;/home/hjshin/wav2vec2/wav2vec2-bert/dataset/240808/valid_under_10.csv&quot;}
    features = Features({
        'audio': Value('string'),
        'sentence': Value('string'),
    })

    print(&quot;1. IterableDataset 로딩 (실제 데이터는 아직 거의 안 읽음)&quot;)
    my_iterable_dataset = load_dataset(&quot;csv&quot;, data_files=data_files, streaming=True, features=features)

    print(&quot;2. 전처리 함수 연결 (지금 당장 실행되는 건 아님)&quot;)
    my_iterable_dataset_processed = my_iterable_dataset.map(remove_special_characters)
    my_iterable_dataset_processed = my_iterable_dataset_processed.map(audio_preprocess)
    my_iterable_dataset_processed = my_iterable_dataset_processed.map(process_fn)

    print(&quot;3. 여기서부터 실제로 데이터를 하나씩 읽고 처리&quot;)
    for i, example in enumerate(my_iterable_dataset_processed['valid']):
        print(f&quot;\n--- {i+1}번째 샘플 ---&quot;)
        print(&quot;오디오 경로:&quot;, example.get('audio', {}).get('path', 'N/A'))
        print(&quot;문장:&quot;, example['sentence'])
        print(&quot;Input Features 길이:&quot;, len(example['input_features']))
        print(&quot;Labels:&quot;, example['labels'])
        if i == 0:
            break
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심은 세 가지다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. load_dataset(..., streaming=True)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;mdash; 데이터셋 객체는 생성되지만, 실제 데이터는 메모리에 거의 올라오지 않는다. 파일 경로와 구조 정도만 파악한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. .map(함수)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;mdash; remove_special_characters, audio_preprocess, input_process를 map으로 연결해도 당장 전체 데이터에 적용되지 않는다. &quot;이 샘플이 오면 이 순서대로 처리해야지&quot; 하고 계획만 잡아두는 것이다. 지연 평가(Lazy Evaluation)라고도 부른다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. for example in ...:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;mdash; 이 루프에 진입할 때 비로소 데이터가 움직인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CSV에서 한 줄 읽기 &amp;rarr; remove_special_characters &amp;rarr; audio_preprocess(이때 오디오 파일 로드) &amp;rarr; input_process(피처 생성) 순으로 처리된 뒤, 그 샘플 하나가 루프 안으로 들어온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 다음 샘플에 대해 같은 과정을 반복한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과적으로 어느 순간에도 메모리에는 지금 처리 중인 샘플 하나 분량의 데이터만 올라와 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;장점 정리&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메모리 부담이 크게 줄어든다. RAM 용량보다 훨씬 큰 데이터셋도 문제없이 처리할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음부터 전체 데이터를 로드하고 전처리가 끝날 때까지 기다릴 필요가 없다. 첫 번째 샘플부터 바로 처리를 시작할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;.map()으로 전처리 함수들을 연결하는 방식이 직관적이라, 파이프라인을 구성하고 수정하기 편하다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;이런 상황에 쓰면 좋다&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터셋이 너무 커서 통째로 메모리에 올리기 어려울 때&lt;/li&gt;
&lt;li&gt;오디오, 영상, 고해상도 이미지처럼 개별 파일이 무거울 때&lt;/li&gt;
&lt;li&gt;클라우드 VM이나 개인 노트북처럼 RAM이 넉넉하지 않은 환경&lt;/li&gt;
&lt;li&gt;특정 인덱스로 랜덤 접근할 필요 없이 순차적으로 읽어도 되는 경우 (딥러닝 학습 대부분이 여기 해당)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;알아두면 좋은 제약&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;셔플링은 완전하지 않다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;shuffle(buffer_size=N)은 N개를 버퍼에 담아 그 안에서만 섞는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 데이터셋을 완전히 랜덤하게 섞으려면 모든 데이터를 메모리에 올려야 하는데, 그러면 스트리밍의 의미가 없어진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;dataset[i]처럼 인덱스로 특정 데이터에 바로 접근하는 건 불가능하다. 항상 순서대로만 읽을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에폭(epoch)을 여러 번 돌리면 매번 파일을 처음부터 다시 읽고, 모든 .map() 연산도 다시 수행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반 Dataset은 처리 결과를 캐싱하기도 하는데, IterableDataset은 그렇지 않다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 가지 더.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;몇 시간 동안 전체 데이터셋을 로드하고 전처리까지 마쳤는데, 특정 데이터 하나의 에러로 학습이 중단돼 처음부터 다시 해야 하는 상황이 생길 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럴 때도 IterableDataset이 유용하다. 문제가 되는 데이터를 빠르게 찾아 처리한 뒤 다시 시작하면 되기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고: &lt;a href=&quot;https://huggingface.co/docs/datasets/about_mapstyle_vs_iterable&quot;&gt;Differences between Dataset and IterableDataset&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Pytorch</category>
      <category>huggingface</category>
      <category>iterabledataset</category>
      <category>oom</category>
      <category>pytorch</category>
      <author>Joy Shin</author>
      <guid isPermaLink="true">https://storyofhz.tistory.com/1</guid>
      <comments>https://storyofhz.tistory.com/1#entry1comment</comments>
      <pubDate>Mon, 19 May 2025 21:20:01 +0900</pubDate>
    </item>
  </channel>
</rss>