LLM Under the Hood 입문자에서 LLM Engineer까지 LLM이라는 Black Box의 Hood를 열어 핵심 기술을 하나씩 이해하고, 마지막에는 다시 하나의 시스템으로 조립해 보는 과정. Series Roadmap 00. LLM은 어떻게 동작할까? 완성된 LLM의 구조를 먼저 살펴보고, 앞으로 공부할 기술들이 어디에 위치하는지 파악한다. Language Model → Next Token Prediction → Token → Embedding → Transformer → Generation 01. 모델의 연산을 이해하기 — Vector & NumPy Embedding과 Attention을 이해하기 위해 필요한 최소한의 수치 연산을 다룬다. Vector · Matrix · Tensor · NumPy · Dot Product · Cosine Similarity · Softmax 02. Text에서 Vector까지 — Tokenization & Embedding 입력된 텍스트가 모델이 처리할 수 있는 형태로 변환되는 과정을 살펴본다. Tokenization · Subword / BPE · Token ID · Word2Vec · Token Embedding · Positional Encoding 03. 문맥과 관계를 계산하기 — Attention & Transformer Token 사이의 관계와 문맥 정보를 모델링하는 핵심 구조를 이해한다. RNN → Attention → Q·K·V → Self-Attention → Multi-Head Attention → Transformer → GPT 04. Transformer에서 LLM으로 — Training & Inference Transformer가 대규모 데이터에서 학습되고 실제 출력을 생성하는 과정을 연결한다. Next Token Prediction · Pre-training · Loss · Instruction Tuning · Logits · Probability · Temperature · Top-k / Top-p 05. 모델의 한계를 외부 정보로 보완하기 — RAG 모델 내부의 지식만으로 해결하기 어려운 문제를 Retrieval과 결합하는 방법을 살펴본다. Chunking → Embedding → Vector DB → Similarity Search → Retrieval → LLM 06. LLM을 시스템으로 — Tool Calling & Agent LLM을 API, Database, Search 등의 외부 도구와 연결해 실제 작업을 수행하는 시스템으로 확장한다. Prompt · Function Calling · Tool Use · API / DB · Agent · Workflow 07. Final — 다시 조립하기 지금까지 살펴본 기술을 하나의 Pipeline으로 연결하여 LLM Application을 구현한다. Vector ↓ Tokenization ↓ Embedding ↓ Attention ↓ Transformer ↓ LLM ↓ RAG / Tool Calling ↓ LLM Application Why LLM Under the Hood? 내가 LLM에 깊이 관심을 갖게 된 계기는 단순히 생성형 AI라는 새로운 기술의 등장 때문만은 아니었다. 언어학과 언어인지과학을 공부해 온 배경에서 언어모델을 접하면서 한 가지 질문이 생겼다. “내가 가지고 있는 관점과 인사이트가 언어모델의 고도화에 기여할 수 있는 지점이 있지 않을까?” 그리고 나와 같은 배경을 가진 사람이 모델을 단순히 활용하는 것을 넘어 모델 자체를 이해하고 기술적인 문제까지 다룰 수 있다면 , 이 분야에서 분명히 할 수 있는 역할이 있을 것이라고 생각했다. 그 생각이 내가 AI와 NLP를 더 깊게 공부하기 시작한 출발점이었다. 하지만 기존에 가지고 있던 지식을 실제 모델의 문제와 연결하려면 또 다른 영역을 이해해야 했다. 모델이 입력을 어떤 단위로 받아들이는지, 정보가 어떤 형태로 표현되는지, Transformer 내부에서는 어떤 연산이 이루어지는지, 학습된 모델이 어떤 과정을 거쳐 결과를 만들어내는지. 모델의 결과를 관찰하는 것에서 그치는 것이 아니라 그 결과가 만들어지는 과정까지 이해할 필요가 있었다. 그래서 기술 스택을 공부하기 시작했다. NumPy 와 Vector 에서 시작해 Embedding , Attention , Transformer 를 지나 RAG , Tool Calling , Agent 까지. 처음에는 서로 다른 기술처럼 보였던 개념들이 하나씩 연결되기 시작했고, 그 연결 구조 자체가 LLM을 이해하는 하나의 지도가 되었다. 이 시리즈는 그 과정을 정리한 기록이다. 무엇을 많이 아는 것보다, 연결해서 이해하기 LLM을 공부하기 시작하면 굉장히 빠르게 수많은 기술을 만나게 된다. NumPy, Word2Vec, RNN, LSTM, Attention, Transformer, BERT, GPT, Fine-tuning, LoRA, Vector DB, RAG, Agent... 모두 중요한 기술이다. 하지만 이 시리즈에서 이 모든 기술을 하나씩 깊게 다루지는 않는다. AI나 컴퓨터공학 전반을 훑는 것이 목적이 아니기 때문이다. 이 시리즈의 기준은 하나다. “이 개념을 모르고 넘어가면 LLM의 동작과 이후 기술의 연결을 이해하기 어려운가?” 필요하다면 직접 다룬다. 그렇지 않다면 전체 흐름을 이해하는 데 필요한 정도에서만 언급한다. 예를 들어 RNN은 Transformer가 등장하게 된 배경을 이해하기 위해 살펴보지만, RNN·LSTM·GRU를 각각 깊게 구현하지는 않는다. Word2Vec 역시 Embedding이라는 중요한 전환점을 이해하기 위해 다루지만, 모든 전통적인 Embedding 알고리즘을 비교하는 것이 목표는 아니다. 많은 기술을 아는 것보다 중요한 기술들이 왜 필요했고, 어떻게 연결되어 지금의 LLM까지 이어졌는지를 이해하는 것. 그것이 이 시리즈에서 정한 범위다. Language → Number → Context → Generation 이 시리즈를 관통하는 하나의 흐름이다. LLM이 만들어내는 결과만 보면 매우 복잡한 시스템처럼 보이지만, Hood를 열어보면 몇 가지 중요한 변환 과정을 발견할 수 있다. 가장 먼저 입력을 모델이 처리할 수 있는 계산 가능한 형태 로 표현해야 한다. Language ↓ Tokenization ↓ Token ↓ Embedding ↓ Vector 여기서 첫 번째 질문이 생긴다. 의미와 정보를 숫자로 표현한다는 것은 무엇일까? 이를 이해하기 위해 Vector, Embedding, Similarity와 같은 개념이 필요해진다. 하지만 각각의 Token을 Vector로 표현하는 것만으로는 충분하지 않다. 같은 Token이라도 주변 정보에 따라 의미와 역할이 달라질 수 있고, 하나의 입력 안에서도 각 Token은 서로 다른 관계를 가진다. 그래서 다음 단계는 Context 다. Vector ↓ Token 간 관계 ↓ Attention ↓ Contextual Representation ↓ Transformer Attention을 통해 각각의 Token이 다른 Token과 어떤 관계를 가지는지 계산하고, Transformer는 이러한 연산을 반복하며 입력의 문맥적 표현을 구성한다. 그리고 학습된 모델은 지금까지 주어진 Context를 바탕으로 다음 Token에 대한 확률을 계산한다. Context ↓ Transformer ↓ Logits ↓ Probability ↓ Next Token ↓ Generation 결국 이 시리즈에서 따라가려는 핵심 흐름은 다음과 같다. Language ↓ Number ↓ Context ↓ Generation 그리고 이 네 단계 사이를 연결하는 것이 Tokenization → Vector → Embedding → Attention → Transformer → Next Token Prediction 이다. 처음에는 각각 독립된 기술처럼 보이지만, 이 흐름 안에서 바라보면 왜 앞의 개념을 알아야 다음 개념을 이해할 수 있는지 가 보이기 시작한다. 기술을 하나씩 외우지 않고 연결해서 보기 각 기술은 독립된 챕터처럼 보이지만 실제로는 계속해서 다음 단계와 연결된다. 예를 들어 Dot Product 는 단순한 수학 개념으로 끝나지 않는다. Vector ↓ Dot Product ↓ Attention Score ↓ Self-Attention ↓ Transformer ↓ LLM Cosine Similarity 역시 앞에서 한 번 배우고 끝나는 개념이 아니다. Embedding ↓ Vector ↓ Cosine Similarity ↓ Similarity Search ↓ Vector DB ↓ RAG 앞에서 배운 개념이 몇 편 뒤 전혀 다른 기술 안에서 다시 등장한다. 그때 “그래서 이걸 배웠구나.” 라고 연결되는 순간을 만드는 것이 이 시리즈에서 중요하게 생각하는 부분이다. 기술을 개별적인 지식으로 쌓는 것이 아니라, 하나의 시스템 안에서 서로 연결된 구조로 이해하는 것 이 목표다. Why → How → Code → LLM 각 기술은 가능한 한 동일한 흐름으로 정리한다. 1. Why — 왜 필요한가? 기술의 정의부터 시작하지 않는다. 먼저 어떤 문제를 해결하기 위해 등장했는지 를 살펴본다. 기존 접근 ↓ 한계 ↓ 새로운 문제 ↓ 기술의 등장 Transformer란 무엇인가? 보다 먼저, “기존 방식에는 어떤 문제가 있었기에 Transformer가 필요했을까?” 를 묻는 방식이다. 2. How — 어떻게 동작하는가? 그다음 핵심적인 구조와 연산을 이해한다. 필요하다면 수식도 사용한다. 다만 수식을 암기하거나 수학 자체를 깊게 파고드는 것이 목적은 아니다. 이 연산이 모델 내부에서 무엇을 계산하고 있는가? 를 이해할 수 있는 수준까지 다루려고 한다. 3. Code — 직접 확인한다 개념을 이해했다면 Python으로 직접 확인한다. 완성된 라이브러리를 호출하고 결과만 확인하는 데서 끝내지 않고, 필요한 경우 핵심 연산을 직접 구현한다. 예를 들어 Cosine Similarity를 배운다면, Vector ↓ Dot Product ↓ Magnitude ↓ Cosine Similarity 가 실제 코드에서는 어떻게 계산되는지 확인한다. Attention 역시 가능하다면 완성된 Transformer 모델을 호출하기 전에 핵심 연산을 직접 구현해 본다. 조금씩 Black Box의 범위를 줄여가는 과정이다. 4. LLM — 그래서 어디에 쓰일까? 마지막에는 항상 같은 질문을 던진다. 그래서 이 기술은 LLM의 어디에서, 왜 사용되는가? Why ↓ How ↓ Code ↓ LLM 개별 기술을 공부하고 다시 전체 구조로 돌아오는 과정을 반복하면서, 하나씩 LLM의 내부를 채워나간다. Why "Under the Hood"? 자동차를 운전하기 위해 엔진의 모든 부품을 알아야 하는 것은 아니다. LLM도 비슷하다. LLM을 활용하기 위해 모델 내부의 모든 원리를 알아야 하는 것은 아니다. API와 Framework를 이용하면 내부 구조를 모두 이해하지 않아도 충분히 복잡한 Application을 만들 수 있다. 하지만 Hood를 한 번 열어보면 이야기가 조금 달라진다. 내가 원하는 것은 LLM을 사용할 수 있는 것에서 한 단계 더 나아가는 것 이다. 예를 들어, "Embedding을 생성한다." → 무엇이 Vector에 표현되는가? "유사한 문서를 검색한다." → '유사하다'는 것은 어떻게 계산되는가? "Attention이 문맥을 반영한다." → 어떤 연산을 통해 관계가 만들어지는가? "LLM이 다음 Token을 예측한다." → 하나의 Token은 어떤 과정으로 선택되는가? "모델이 특정 상황에서 반복적으로 오류를 만든다." → 그 결과를 데이터나 모델의 구조와 연결해서 설명할 수 있을까? 특히 마지막 질문까지 가기 위해서는 Hood를 열어볼 필요가 있다고 생각했다. 모든 부품을 분해하는 것이 목적은 아니다. 모델의 동작과 결과를 보았을 때, 그 현상을 내부 구조와 연결해 생각할 수 있을 정도까지. 이 시리즈에서 말하는 Under the Hood 는 그 정도의 깊이를 의미한다. From Language to LLM Engineering 내가 가지고 있던 전공적 배경은 이 분야에 관심을 갖게 된 중요한 출발점이었다. 하지만 그것만으로 모델의 개선을 이야기할 수 있다고 생각하지는 않았다. 어떤 현상에서 문제를 발견하고 그에 대한 인사이트를 가지고 있더라도, 그것을 실제 AI의 문제와 연결하는 순간부터는 기술적인 이해가 필요하다. 모델의 오류를 발견했다면 그것이 데이터의 문제인지, 표현의 문제인지, Context 처리의 문제인지, Retrieval의 문제인지, 혹은 모델 자체의 특성인지 구분할 수 있어야 한다. 개선 아이디어가 있다면 가설로 끝내지 않고 실험 가능한 형태로 구현하고 결과로 검증할 수 있어야 한다. 그래서 내가 공부해야 할 범위도 자연스럽게 넓어졌다. Domain Insight │ ▼ Problem Definition │ ▼ NLP / ML │ ▼ Transformer / LLM │ ▼ Implementation │ ▼ Experiment & Evaluation │ ▼ LLM Engineering 나에게 기술 스택을 공부하는 과정은 기존에 공부해 온 영역을 버리고 전혀 다른 분야로 이동하는 것이라기보다, 내가 가지고 있던 관점을 실제 모델의 문제와 연결할 수 있도록 새로운 도구를 갖추는 과정 에 가깝다. 내가 처음 이 분야에 더 깊이 들어가야겠다고 생각했던 이유 역시 여기에 있다. 내가 가진 인사이트가 모델의 고도화에 기여할 수 있다면? 그 가능성을 이야기하기 위해서는 먼저 모델을 이해해야 했다. 그리고 단순히 개념적으로 이해하는 것을 넘어, 직접 구현하고 실험하고 검증할 수 있어야 했다. 그래서 Vector 와 Matrix 를 공부하고, Embedding 을 공부하고, Attention 과 Transformer 의 내부를 들여다보기 시작했다. 이 시리즈는 그 과정에서 쌓아가는 기술적 이해를 하나씩 기록하는 공간이기도 하다. Final — 다시 조립하기 시리즈의 시작에서는 LLM을 하나의 완성된 시스템으로 바라본다. User │ ▼ ┌───────────────┐ │ LLM │ └───────────────┘ │ ▼ Answer 그리고 하나씩 Hood를 열어본다. LLM │ ├── Tokenization ├── Embedding ├── Attention ├── Transformer └── Next Token Prediction 각각의 부품을 이해한 뒤에는 다시 실제 Application으로 확장한다. User │ ▼ LLM │ ┌───────┴───────┐ │ │ ▼ ▼ RAG Tool Calling │ │ Embedding API / DB │ │ Vector DB │ │ │ └───────┬───────┘ ▼ LLM │ ▼ Answer 처음에는 각각 별개의 기술처럼 보였던 Vector, Tokenization, Embedding, Attention, Transformer, RAG, Tool Calling 이 마지막에는 하나의 시스템 안에서 연결되어 보이는 것. 그리고 단순히 구조를 이해하는 데서 끝나지 않고 직접 하나의 LLM Application으로 구현해 보는 것. 그것이 이 시리즈의 첫 번째 도착점이다. 하지만 궁극적으로 만들고 싶은 역량은 조금 더 그 너머에 있다. 모델의 결과를 단순히 “잘 된다 / 잘 안 된다” 로 판단하는 데서 끝나지 않고, 문제 발견 ↓ 원인 분석 ↓ 가설 설정 ↓ 구현 ↓ 실험 ↓ 평가 ↓ 개선 의 과정으로 가져갈 수 있는 것. 내가 기존에 가지고 있던 관점과 새롭게 쌓아가는 기술적 이해를 함께 활용해 모델의 문제를 발견하고, 개선 가능성을 실제로 검증할 수 있는 것. 그것이 내가 LLM을 공부하고 기술 스택까지 확장하게 된 이유이자, 이 시리즈를 시작한 이유다. 이 시리즈가 도착하려는 곳 이 시리즈를 끝낸다고 해서 LLM의 모든 것을 알게 되는 것은 아니다. 오히려 앞으로 무엇을 더 공부해야 하는지 판단할 수 있는 하나의 지도 를 만드는 것이 목표에 가깝다. 처음 보는 기술을 만났을 때 단순히 사용법을 외우는 것이 아니라, 이 기술은 어떤 문제를 해결하기 위해 등장했을까? 기존 방식에는 어떤 한계가 있었을까? LLM Pipeline의 어디에 위치할까? 앞에서 공부했던 어떤 개념과 연결될까? 실제 모델의 문제를 해결하는 데 어떻게 활용할 수 있을까? 를 스스로 질문할 수 있는 기반을 만들고 싶다. 익숙한 영역에서 발견한 질문을 AI의 문제로 확장하고, 그 질문을 직접 다룰 수 있기 위해 기술을 공부한다. LLM을 사용하는 것에서, 이해하는 것으로. 이해하는 것에서, 문제를 발견하고 개선하는 것으로. LLM Under the Hood.
계산동 쓰리노 알아보기 위치부터 이용 전 확인사항까지 인천에서 저녁 시간에 모임 장소를 찾다 보면 계산동을 검색하게 되는 경우가 있습니다. 계산동은 계산역을 중심으로 다양한 음식점과 주점, 카페 등이 모여 있는 지역으로, 친구 모임이나 직장인 모임처럼 저녁 시간을 활용하는 일정도 계획하기 좋은 곳입니다. 이번 글에서는 특정 업체를 소개하기보다는 계산동 쓰리노를 알아볼 때 확인하면 좋은 기본적인 정보를 정리해보겠습니다. 처음 검색하는 분이라면 어떤 부분을 확인해야 하는지부터 알아두는 것만으로도 장소를 선택하고 일정을 준비하는 과정이 훨씬 간단해집니다. 계산동 쓰리노를 알아볼 때 먼저 확인할 부분 계산동에서 관련 장소를 찾을 때 가장 먼저 확인할 부분은 위치입니다. 계산역을 중심으로 상권이 형성되어 있기 때문에 지하철을 이용하는 경우에는 역에서 얼마나 떨어져 있는지 확인하면 좋습니다. 특히 여러 명이 모이는 자리라면 참석자들의 이동 방법이 서로 다를 수 있습니다. 누군가는 지하철을 이용하고 다른 사람은 차량을 이용할 수도 있기 때문에 약속 장소를 정하기 전에 대중교통 접근성과 주차 가능 여부를 함께 확인하면 편리합니다. 장소의 정확한 위치와 건물명, 주변 랜드마크 등을 미리 확인해두면 처음 방문하는 사람도 쉽게 찾아갈 수 있습니다. 계산역 주변 상권의 특징 계산동에서 모임 장소를 찾을 때 계산역 주변을 함께 살펴보는 이유는 다양한 상업시설이 모여 있기 때문입니다. 저녁 식사를 먼저 하고 다음 일정으로 이동하거나, 모임 이후 카페에서 시간을 보내는 등 여러 가지 방식으로 일정을 구성할 수 있습니다. 예를 들어 저녁 식사 시간을 먼저 정하고 이후 별도의 공간에서 모임을 이어가는 방식으로 계획할 수 있습니다. 여러 명이 함께하는 경우에는 장소 사이의 이동거리도 중요한 요소입니다. 이동거리가 짧으면 일행이 함께 움직이기 편하고, 늦게 합류하는 사람도 약속 장소를 찾기 수월합니다. 쓰리노라는 용어의 의미 쓰리노라는 표현은 유흥업계에서 특정한 형태의 이용 방식을 설명할 때 사용되는 용어입니다. 일반적인 노래방이나 주점과는 공간 구성이나 운영 방식에서 차이가 있을 수 있으며, 독립된 룸을 이용하는 형태가 포함되는 경우가 있습니다. 다만 같은 명칭을 사용하더라도 업체마다 실제 운영 방식이나 제공되는 서비스가 다를 수 있습니다. 따라서 인터넷에서 본 용어만으로 이용 형태를 판단하기보다는 방문하려는 장소에 직접 문의해 구체적인 운영 방식과 이용 조건을 확인하는 것이 좋습니다. 인원수에 맞는 룸 선택 모임을 계획할 때 인원수는 공간을 선택하는 중요한 기준입니다. 2~3명처럼 소규모로 방문하는 경우와 여러 명이 함께 방문하는 경우에는 필요한 공간의 크기가 달라집니다. 인원이 많다면 좌석 수뿐만 아니라 테이블 크기와 룸 내부 공간까지 함께 확인하는 것이 좋습니다. 특히 일행 가운데 늦게 합류하는 사람이 있다면 예약 단계에서 예상 인원을 이야기해두는 것이 편리합니다. 최종 인원이 변경될 가능성이 있다면 처음부터 해당 내용을 전달하고 인원 변경이 가능한지도 확인하면 됩니다. 이용시간은 미리 정하기 저녁 모임은 식사부터 시작해 여러 장소를 이동하는 경우가 많기 때문에 이용시간을 미리 정해두는 것이 좋습니다. 기본 이용시간이 정해져 있다면 시작시간과 종료시간을 확인하고, 일정이 길어질 경우 추가 이용이 가능한지도 알아볼 수 있습니다. 특히 대중교통을 이용하는 일행이 있다면 귀가 시간까지 고려하는 것이 좋습니다. 모임을 시작하는 시간뿐 아니라 종료 예상시간까지 정해두면 이후 일정이나 귀가 계획을 세우기가 편합니다. 비용은 전체 구성으로 확인하기 장소를 선택할 때 비용은 단순히 기본 금액 하나만 확인하기보다 전체적인 구성을 살펴보는 것이 좋습니다. 기본 이용료 외에 주류, 이용시간, 인원 등에 따라 별도의 비용이 발생할 수 있기 때문입니다. 따라서 방문 전에 다음과 같은 내용을 확인해두면 좋습니다. 기본 이용료 기본 이용시간 주류 비용 추가 이용시간 비용 인원에 따른 비용 차이 별도로 발생할 수 있는 추가 비용 결제 방식 특히 온라인에서 확인한 금액과 실제 이용 조건이 다를 수 있으므로 방문 시점의 정확한 비용을 문의하는 것이 가장 확실합니다. 주류와 이용 구성 확인하기 모임의 성격에 따라 주류 이용 여부도 미리 정할 수 있습니다. 주류를 이용할 예정이라면 어떤 종류가 준비되어 있는지, 기본 이용에 포함되는지 별도로 계산되는지 등을 확인하는 것이 좋습니다. 여러 명이 함께 방문하는 경우에는 일행마다 원하는 메뉴가 다를 수 있기 때문에 미리 예산을 정해두면 전체적인 지출을 관리하기 편합니다. 또한 이용시간을 연장할 계획이 있다면 연장 기준과 추가 비용까지 함께 확인하는 것이 좋습니다. 주차 여부도 체크하기 계산동은 대중교통으로 접근하기 편리한 편이지만 차량을 이용하는 방문객도 많기 때문에 주차 여부를 확인해두는 것이 좋습니다. 차량으로 방문한다면 건물 내 주차장이 있는지, 무료 주차가 가능한지, 이용시간에 따른 주차 조건이 있는지 등을 확인할 수 있습니다. 여러 명이 각각 차량을 이용한다면 주차 공간의 규모도 고려할 필요가 있습니다. 주차 조건은 건물이나 업소에 따라 달라질 수 있으므로 방문 전에 직접 확인하는 것이 좋습니다. 예약 전에 전달하면 좋은 정보 예약이나 방문 문의를 할 때는 필요한 정보를 한 번에 전달하면 보다 원활하게 안내받을 수 있습니다. 기본적으로 날짜와 시간, 인원수를 먼저 전달하고 원하는 이용시간과 공간 형태가 있다면 함께 이야기하는 것이 좋습니다. 예를 들어 다음과 같이 정리할 수 있습니다. 방문 날짜 → 방문 시간 → 인원 → 예상 이용시간 → 원하는 룸 형태 → 주차 여부 이렇게 필요한 내용을 미리 정리하면 문의 과정도 간단해집니다. 인원수가 확정되지 않은 경우에는 예상 인원을 이야기하고 이후 변경이 가능한지도 함께 확인하면 됩니다. 계산동에서 함께 계획하기 좋은 일정 계산동에서 저녁 약속을 잡는다면 한 장소만 정하기보다 전체 동선을 함께 생각해보는 것도 좋습니다. 저녁 식사를 먼저 한 뒤 모임 장소로 이동하거나, 일정이 끝난 후 카페 등에서 추가로 시간을 보내는 방식도 가능합니다. 계산역 주변을 기준으로 일정을 잡으면 일행들이 모이기 편하고, 처음 방문하는 사람에게도 위치를 설명하기 쉽습니다. 차량을 이용하는 사람이 있다면 주차가 가능한 장소를 기준으로 동선을 정하는 방법도 있습니다. 처음 알아볼 때 체크리스트 계산동 쓰리노를 처음 알아본다면 아래 항목을 순서대로 확인해보는 것이 좋습니다. 위치 계산역과의 거리와 정확한 건물 위치를 확인합니다. 인원 방문 예정 인원을 정확하게 전달합니다. 시간 입장시간과 기본 이용시간을 확인합니다. 비용 기본 이용료와 주류, 추가시간 등의 비용을 확인합니다. 공간 인원에 적합한 룸과 좌석 구성을 문의합니다. 주차 차량을 이용한다면 주차 가능 여부와 조건을 확인합니다. 추가 조건 특별히 원하는 이용 조건이 있다면 방문 전에 미리 문의합니다. 마무리 계산동 쓰리노를 알아볼 때는 단순히 장소 이름만 확인하기보다 위치, 접근성, 인원, 이용시간, 비용, 공간 구성, 주차 여부를 함께 살펴보는 것이 좋습니다. 특히 계산역 주변은 다양한 상업시설이 모여 있어 식사와 모임을 하나의 일정으로 구성하기 편리합니다. 처음 방문하는 경우라면 원하는 날짜와 시간, 인원수를 먼저 정하고 예산과 이용시간까지 정리한 뒤 문의하는 방법이 가장 간단합니다. 비용 역시 기본 이용료만 확인하기보다는 주류와 추가 이용시간 등 전체적인 구성까지 확인하면 예상 지출을 보다 정확하게 파악할 수 있습니다. 가성비 업장 중심 (추천형)화려한 겉모습에 속기보다는 실제로 다녀온 사람들의 평점이 높고 마진을 낮춘 알짜배기 업장을 고르는 것이 핵심입니다. 리얼 피드백으로 엄선된 [ 검증된 데이터를 기반으로 한 계산동쓰리노 가성비 업장 추천 ] 리스트를 확인해 보시기 바랍니다. 무엇보다 업체마다 운영 방식과 이용 조건이 다를 수 있기 때문에 온라인에서 확인한 정보만으로 판단하기보다는 방문하는 시점의 정확한 조건을 직접 확인하는 것이 좋습니다. 계산동에서 저녁 모임을 계획하고 있다면 계산역 주변의 접근성과 일행들의 이동 방법을 함께 고려하면서 자신에게 맞는 공간을 찾아보면 보다 편리하게 일정을 구성할 수 있습니다.
지금까지 Raspberry Pi 5와 Pico 2 W를 이용해 다음 내용을 살펴봤다. Raspberry Pi SSH ↓ Pico 2 W MicroPython ↓ GPIO ↓ Serial / UART ↓ Wi-Fi ↓ Network Communication 각 기능을 개별적으로 테스트하는 단계에서는 파일 몇 개만 있어도 충분하다. 하지만 센서, 데이터 저장, 통신, 분석 기능이 추가되기 시작하면 하나의 파일에 모든 코드를 작성하기 어렵다. 예를 들어 프로젝트가 다음과 같이 커질 수 있다. Sensor ↓ Pico 2 W ↓ UART / Wi-Fi ↓ Raspberry Pi 5 ↓ Data Processing ↓ CSV / Database ↓ Visualization ↓ Machine Learning 이 단계부터는 프로젝트 구조를 처음부터 잘 나누는 것 이 중요하다. 이번 글에서는 Raspberry Pi 5와 Pico 2 W를 함께 사용하는 프로젝트를 GitHub에서 관리할 수 있도록 기본적인 프로젝트 구조를 설계한다. 1. Raspberry Pi 5와 Pico 2 W의 역할부터 나누기 폴더를 만들기 전에 두 장치의 역할을 먼저 구분하는 것이 좋다. Pico 2 W Pico 2 W는 마이크로컨트롤러이므로 하드웨어와 가까운 작업을 담당한다. Pico 2 W ├─ GPIO ├─ ADC ├─ PWM ├─ Sensor Reading ├─ Timing ├─ Hardware Control ├─ UART └─ Wi-Fi 즉, Physical Layer 에 가까운 역할을 담당한다. Raspberry Pi 5 Raspberry Pi 5는 Linux 기반 컴퓨터이므로 상대적으로 복잡한 작업을 담당한다. Raspberry Pi 5 ├─ Data Collection ├─ Data Processing ├─ File Storage ├─ Database ├─ Visualization ├─ Networking ├─ Web Server ├─ Signal Processing ├─ Machine Learning └─ System Management 즉, Application / Processing Layer 역할을 맡는다. 2. 전체 시스템 구조 프로젝트 전체를 다음과 같이 생각할 수 있다. ┌─────────────────────────┐ │ Physical World │ │ │ │ Sensor / Electrode │ │ Button / LED / Motor │ └───────────┬─────────────┘ │ ▼ ┌─────────────────────────┐ │ Pico 2 W │ │ │ │ GPIO / ADC / PWM │ │ Sensor Acquisition │ │ Hardware Control │ └───────────┬─────────────┘ │ │ UART / USB / Wi-Fi │ ▼ ┌─────────────────────────┐ │ Raspberry Pi 5 │ │ │ │ Python │ │ Data Processing │ │ Storage │ │ Visualization │ │ Network │ └───────────┬─────────────┘ │ ▼ ┌─────────────────────────┐ │ Mac / GitHub / Server │ └─────────────────────────┘ 이 구조를 그대로 프로젝트 폴더에도 반영하면 관리하기 쉬워진다. 3. Git 저장소는 하나로 관리하기 Raspberry Pi 코드와 Pico 코드를 각각 다른 Repository로 만들어도 되지만 처음에는 하나의 Repository 안에서 분리하는 방식 이 관리하기 편하다. 예를 들어 프로젝트 이름을 BioLoop-Pi 라고 한다면 BioLoop-Pi/ │ ├── pi/ │ └── pico/ 처럼 구성한다. 즉, 하나의 프로젝트 ↓ 하나의 Git Repository ↓ Pi 코드 + Pico 코드 구조이다. 4. 기본 프로젝트 구조 처음 추천하는 구조는 다음과 같다. BioLoop-Pi/ │ ├── README.md ├── .gitignore │ ├── pi/ │ ├── app/ │ │ ├── main.py │ │ ├── serial_reader.py │ │ ├── network_client.py │ │ └── data_logger.py │ │ │ ├── tests/ │ │ │ └── requirements.txt │ ├── pico/ │ ├── main.py │ ├── app.py │ ├── wifi.py │ ├── communication.py │ ├── sensors.py │ │ │ ├── lib/ │ │ │ └── secrets.example.py │ ├── data/ │ ├── raw/ │ └── processed/ │ ├── scripts/ │ ├── docs/ │ └── logs/ 프로젝트가 작을 때는 다소 복잡해 보일 수 있지만 기능이 추가되기 시작하면 이런 구조가 훨씬 관리하기 쉽다. 5. 각각의 폴더 역할 pi/ Raspberry Pi 5에서 실행되는 프로그램을 저장한다. pi/ │ ├── app/ ├── tests/ └── requirements.txt Raspberry Pi에서는 일반 Python을 사용한다. 예: import serial import json import time pico/ Pico 2 W에서 실행되는 MicroPython 코드를 저장한다. pico/ │ ├── main.py ├── app.py ├── wifi.py ├── communication.py ├── sensors.py └── lib/ 이 폴더의 코드는 Pico 내부 Flash로 복사해 실행하게 된다. data/ 센서 데이터를 저장한다. data/ │ ├── raw/ │ └── processed/ raw 는 원본 데이터, data/raw/ processed 는 필터링이나 분석이 끝난 데이터로 구분할 수 있다. data/processed/ docs/ 프로젝트 문서를 저장한다. docs/ │ ├── architecture.md ├── hardware.md ├── protocol.md └── experiments.md 예를 들어 hardware.md → 회로 구성 protocol.md → Pi ↔ Pico 통신 규칙 experiments.md → 실험 기록 처럼 관리할 수 있다. scripts/ 개발 과정에서 반복적으로 사용하는 명령을 자동화한다. 예: scripts/ ├── deploy_pico.sh ├── run_pi.sh └── setup_pi.sh 프로젝트가 어느 정도 커진 뒤 추가해도 된다. logs/ 프로그램 실행 로그를 저장한다. logs/ ├── system.log ├── sensor.log └── error.log 로그 파일은 크기가 계속 증가할 수 있기 때문에 일반적으로 Git에는 포함하지 않는다. 6. Mac을 개발의 기준 Repository로 사용하기 개인 프로젝트라면 Mac에 있는 Git Repository를 주 개발 원본 으로 관리하는 방식이 편하다. 구조는 다음과 같다. Mac │ │ Git ▼ GitHub │ │ git clone / git pull ▼ Raspberry Pi 5 Pico 코드는 같은 Repository의 pico/ 폴더에서 관리한다. Mac │ ├─ pi/ │ └─ pico/ │ │ USB ▼ Pico 2 W 즉 Git Repository 자체를 Pico에 저장하는 것이 아니다. 7. Pico는 Git Repository가 아니다 이 부분이 중요하다. Pico 내부에는 MicroPython Flash 파일 시스템이 있지만 Linux 환경이 아니다. 따라서 Pico 안에서 git status git pull git push 같은 명령을 사용하는 구조가 아니다. Git의 기준 파일은 Mac이나 Raspberry Pi에 저장한다. Git Repository │ ▼ Mac / Raspberry Pi │ │ 필요한 MicroPython 파일 복사 ▼ Pico 2 W 즉 Pico 내부의 파일은 배포된 실행본 이라고 생각하면 이해하기 쉽다. 8. Pico 코드도 항상 Repository에 원본을 보관하기 예를 들어 Pico에서 사용하는 파일이 다음과 같다고 하자. main.py wifi.py sensors.py communication.py 이 파일의 원본은 BioLoop-Pi/pico/ 에 둔다. 그리고 필요한 파일을 Pico 내부로 복사한다. Git Repository pico/ ├── main.py ├── wifi.py ├── sensors.py └── communication.py │ │ Deploy ▼ Pico Flash / ├── main.py ├── wifi.py ├── sensors.py └── communication.py 이렇게 하면 Pico가 고장 나거나 Firmware를 다시 설치해도 코드를 Git에서 복원할 수 있다. 9. Pico의 main.py 는 최대한 단순하게 만들기 처음에는 모든 코드를 main.py 하나에 작성하기 쉽다. 예: import network import socket from machine import Pin, ADC # 수백 줄의 코드... 하지만 프로젝트가 커지면 관리하기 어렵다. 대신 main.py 는 프로그램의 Entry Point 역할만 담당하게 만든다. 예: import app app.main() 그리고 실제 프로그램은 app.py 에서 실행한다. MicroPython의 공식 문서에서도 복잡한 프로그램에서는 main.py 를 간단한 진입점으로 두고 실제 애플리케이션 모듈을 import하여 실행하는 구조를 사용할 수 있다고 설명한다. 10. Pico 애플리케이션 구조 예를 들어 다음과 같이 나눌 수 있다. pico/ │ ├── main.py ├── app.py ├── sensors.py ├── communication.py ├── wifi.py └── lib/ main.py import app app.main() app.py import time from sensors import read_sensor from communication import send_data def main(): while True: value = read_sensor() send_data(value) time.sleep(1) sensors.py from machine import ADC sensor = ADC(26) def read_sensor(): return sensor.read_u16() 이렇게 기능별로 파일을 나누게 된다. 11. MicroPython의 부팅 파일 MicroPython에서는 boot.py main.py 가 특별한 의미를 가진다. 기본적인 실행 순서는 Pico Power ON ↓ boot.py ↓ main.py 이다. MicroPython 문서에서도 boot.py 가 먼저 실행되고 이후 main.py 가 실행되는 부팅 구조를 설명한다. 다만 처음 프로젝트를 만들 때는 반드시 boot.py 를 만들 필요는 없다. 12. boot.py 에 모든 것을 넣지 않기 boot.py 는 초기화 용도로 사용하는 것이 좋다. 예를 들어 Hardware 초기 설정 Network 초기 설정 등을 넣을 수 있다. 하지만 프로그램 전체를 boot.py 안에서 무한 반복시키는 방식은 피하는 것이 좋다. MicroPython 문서 역시 boot.py 는 종료되는 초기화 코드에 적합하며 실제 애플리케이션은 main.py 에서 실행하는 구조를 권장한다. 처음에는 오히려 boot.py 없음 main.py → app.py 실행 정도로 시작해도 충분하다. 13. Raspberry Pi 코드도 기능별로 나누기 Raspberry Pi에서도 모든 코드를 하나의 파일에 넣지 않는다. 예를 들어 pi/app/ │ ├── main.py ├── serial_reader.py ├── network_client.py ├── data_logger.py └── config.py 처럼 구성한다. 14. Raspberry Pi main.py 예: from serial_reader import SerialReader from data_logger import DataLogger def main(): reader = SerialReader() logger = DataLogger() while True: data = reader.read() if data is not None: logger.save(data) if __name__ == "__main__": main() main.py 역시 전체 프로그램의 흐름만 관리하게 만든다. 15. Serial 코드 분리 serial_reader.py import serial class SerialReader: def __init__( self, port="/dev/ttyAMA0", baudrate=115200 ): self.serial = serial.Serial( port, baudrate, timeout=1 ) def read(self): data = self.serial.readline() if not data: return None return ( data .decode("utf-8") .strip() ) 이렇게 하면 Serial 관련 코드를 다른 부분과 분리할 수 있다. 16. 데이터 저장 코드 분리 data_logger.py from datetime import datetime from pathlib import Path class DataLogger: def __init__(self): self.path = Path( "../../data/raw/sensor.csv" ) def save(self, value): timestamp = datetime.now().isoformat() with self.path.open( "a", encoding="utf-8" ) as file: file.write( f"{timestamp},{value}\n" ) 프로젝트가 커지면 Sensor Communication Storage Processing 기능이 서로 섞이지 않게 된다. 17. Raspberry Pi에서는 Python 가상환경 사용하기 Raspberry Pi의 Python 프로젝트에서는 가상환경 을 사용하는 것이 좋다. 가상환경을 사용하면 프로젝트에 필요한 Python 패키지를 시스템 Python과 분리해서 관리할 수 있다. Python 공식 문서에서도 venv 는 프로젝트별로 독립된 Python 패키지 환경을 생성하기 위한 기능이며 일반적으로 .venv 같은 디렉터리를 사용한다고 설명한다. 프로젝트 폴더에서 cd BioLoop-Pi/pi 가상환경을 만든다. python3 -m venv .venv 환경에 따라 venv 패키지가 없다면 먼저 설치한다. sudo apt install python3-venv 18. 가상환경 활성화 source .venv/bin/activate 성공하면 Terminal 앞부분에 (.venv) 가 나타난다. 예: (.venv) minwoo@raspberrypi:~/BioLoop-Pi/pi $ 이 상태에서 설치한 Python 패키지는 해당 프로젝트 환경에 설치된다. 19. 필요한 패키지 설치 예를 들어 UART를 사용한다면 python -m pip install pyserial 추후 데이터 처리에 NumPy를 사용한다면 python -m pip install numpy 처럼 설치한다. 가상환경 안에서 pip 를 사용하면 프로젝트에 필요한 패키지를 시스템 환경과 분리할 수 있다. 20. requirements.txt 프로젝트에 필요한 Python 패키지를 기록한다. 예: pi/requirements.txt 내용: pyserial numpy 다른 Raspberry Pi에서 프로젝트를 실행할 때는 python -m pip install -r requirements.txt 로 필요한 패키지를 설치할 수 있다. 21. .venv 는 GitHub에 올리지 않는다 Python 가상환경은 다른 컴퓨터에서 다시 생성할 수 있기 때문에 Git으로 관리하지 않는다. Python 공식 문서 역시 가상환경은 소스 관리 시스템에 포함하지 않고 필요하면 다시 생성하는 것을 전제로 한다. 따라서 .gitignore 에 .venv/ 를 추가한다. 22. .gitignore 기본 구성 프로젝트 루트에 .gitignore 파일을 만든다. 예: # Python virtual environment .venv/ venv/ # Python cache __pycache__/ *.pyc # macOS .DS_Store # Secrets secrets.py .env # Logs logs/ *.log # Experimental data data/raw/ data/processed/ # IDE .vscode/ .idea/ 필요에 따라 수정하면 된다. 23. 센서 데이터는 Git에 올려야 할까? 일반적으로 대용량 센서 Raw Data는 Git에 계속 올리지 않는 것이 좋다. 예: data/raw/ 에는 실험을 할 때마다 많은 파일이 생길 수 있다. sensor_001.csv sensor_002.csv sensor_003.csv ... 이런 파일을 모두 Git에 넣으면 Repository 크기가 빠르게 증가한다. 따라서 기본적으로 data/raw/ data/processed/ 로 제외할 수 있다. 24. 폴더 자체는 Git에 남기고 싶다면 Git은 빈 디렉터리를 추적하지 않는다. 따라서 data/raw/ 폴더 안에 .gitkeep 같은 빈 파일을 넣는 방법을 사용할 수 있다. 예: data/ ├── raw/ │ └── .gitkeep │ └── processed/ └── .gitkeep .gitignore 설정에 따라 .gitkeep 예외 규칙을 추가하면 된다. 25. Wi-Fi 비밀번호와 API Key는 코드와 분리하기 다음과 같이 직접 작성하면 ssid = "MyWiFi" password = "12345678" GitHub에 코드가 올라갈 때 비밀번호도 함께 올라갈 수 있다. 따라서 Pico에서는 secrets.py 를 별도로 사용한다. 예: SSID = "MyWiFi" PASSWORD = "12345678" 그리고 secrets.py 로 Git에서 제외한다. 26. secrets.example.py 만들기 하지만 secrets.py 를 완전히 제외하면 다른 사람이 어떤 설정값이 필요한지 알기 어렵다. 따라서 secrets.example.py 를 Repository에 넣는다. SSID = "YOUR_WIFI_NAME" PASSWORD = "YOUR_WIFI_PASSWORD" 사용자는 이 파일을 복사해 secrets.py 를 만들면 된다. 27. Raspberry Pi 설정도 같은 방식으로 관리하기 Raspberry Pi에서 사용하는 설정도 코드와 분리하는 것이 좋다. 예: .env PICO_PORT=/dev/ttyAMA0 BAUD_RATE=115200 그리고 .env 로 제외한다. Repository에는 .env.example 을 저장한다. 예: PICO_PORT=/dev/ttyAMA0 BAUD_RATE=115200 28. Pi와 Pico 사이의 통신 규칙 만들기 프로젝트가 커지면 단순히 값을 하나 보내는 것보다 통신 규칙을 정하는 것이 좋다. 예: TEMP:24.8 ADC:32510 STATUS:OK 또는 JSON을 사용할 수도 있다. { "type": "sensor", "temperature": 24.8, "adc": 32510 } 이 규칙을 docs/protocol.md 에 작성한다. 29. protocol.md 예시 # Communication Protocol Baud Rate 115200 Encoding UTF-8 Message Terminator \n 메시지 종류: TEMP:<value> ADC:<value> STATUS:<value> 예: TEMP:24.8 ADC:32510 STATUS:OK 명령: LED:ON LED:OFF START STOP 이렇게 정해두면 Pi와 Pico 프로그램을 각각 수정하더라도 통신 규칙을 쉽게 확인할 수 있다. 30. 프로젝트 구조를 한 단계 더 정리하기 조금 더 체계적으로 만들면 다음처럼 구성할 수 있다. BioLoop-Pi/ │ ├── README.md ├── .gitignore │ ├── pi/ │ │ │ ├── app/ │ │ ├── main.py │ │ ├── serial_reader