고영민mandu05.com

둘러보기

  • 홈
  • 글
  • 프로젝트
  • 지식 그래프
  • 이력서
본문으로 건너뛰기
  1. 고영민
  2. /
  3. 글
고영민/글
© 2023-2026 Youngmin. All rights reserved.
rhdudals0505@naver.comGitHubLinkedIn
이 사이트는 Next.js로 만들었습니다.갱신 2026.09.26
글/프로젝트 · 해커톤 · 대회 · ML · 데이터 대회

KRAFTON AI R&D 해커톤 예선: 파라미터 7천 개로 곱셈을 배우고, 노이즈 속에서 규칙을 되찾다

2026-03-29·6분 읽기
HackathonKRAFTONPyTorchTransformerCryptographyLPNNumba
← 이전 글 MOLOCO ML 엔지니어 인턴 스크리닝 면접: 잘 풀렸다고 느꼈는데 떨어진 이유를 찾으며 다음 글 →한화오션 2026 상반기 AX·AI개발(DT) 추가합격 회고

See also

3
  • Gemini 서울 해커톤 1,500팀 중 20위: 근로자의 증거를 진정서로 바꾸는 EquiLaw
  • LG Aimers 5기 회고: Stacking 앙상블과 SMOTE-ENN으로 풀어본 제품 이상 여부 판별
  • SoftBank Hackathon 2025: 슬랙 한 줄로 끝나는 무중단 배포를 48시간에 만들다

댓글 불러오는 중…

← 글

목차

  • Day 1 — MultiplierBoard: 6비트 곱셈 트랜스포머
  • 과제 개요
  • 겪은 문제 1: exposure bias
  • 겪은 문제 2: 기울기가 끊겼다
  • 접근 방향
  • Day 1 숫자 (내 제출)
  • Day 2 — SparseTap: LPN 맥락의 LFSR·탭 복구
  • 과제 개요
  • 시도 1: 연속 완화 + SIREN
  • 시도 2: FCA 류 무차별
  • 최종 해법: ISD + 비트 패킹 + Numba
  • Day 2 숫자 (내 로그)
  • 정리하면
  • 본선 진출에 이르지 못한 이유

본선에는 가지 못했지만, 2026년 3월 28~29일 KRAFTON AI R&D 해커톤 온라인 예선 이틀은 최근 한 공부 중 가장 밀도가 높았다. 첫째 날은 정해진 학습 조건 안에서 6비트 곱셈을 해내는 아주 작은 트랜스포머를 만드는 과제(MultiplierBoard), 둘째 날은 20% 노이즈가 섞인 수열에서 숨은 XOR 규칙을 되찾는 과제(SparseTap)였다. 결과부터 말하면 첫날은 파라미터 6,978개로 정확도 99.98%, 둘째 날은 이론 상한 80%에 맞는 79.99%까지 갔다. 이 글은 각 과제에서 막힌 곳, 푼 방법, 그리고 그래도 본선 컷을 넘지 못한 이유를 적는다.

KRAFTON AI R&D HACKATHON 포스터

포스터 기준으로 지원 마감은 2026.03.27 12:00, 1차 예선은 3.28~3.29 온라인, 본선은 4.4~4.5 오프라인이었다.

Day 1 — MultiplierBoard: 6비트 곱셈 트랜스포머

과제 개요

  • 6비트 두 수 A, B를 비트로 펼치고, 곱 P(12비트)를 자기회귀모델로 맞춘다.
  • 학습은 주최 쪽에서 정해 둔 설정(AdamW, 코사인 200 epoch 등)을 지켜야 하고, 검증 정확도 Acc2는 99% 넘기면서 학습 파라미터 수 P2는 최대한 줄인다.
  • 따로 P1이라고, 손으로 깔 수 있는 최소 파라미터 하한을 이론으로 적어 내야 했다.

처음에는 소형 언어 모델을 만드는 것과 비슷한 감각으로 label smoothing, scheduled sampling 등을 적용했고, ReLU에 강한 임계를 두어 AND·캐리를 흉내 내는 방식도 시도했다. 두 방향 모두 검증 정확도나 안정적인 학습으로 이어지지 않았다.

겪은 문제 1: exposure bias

타깃을 0으로 길게 패딩해 두면 모델이 곱셈 절차 대신 “앞부분이 0이면 뒤도 0으로 둔다” 는 지름길을 학습하기 쉽다. teacher forcing 형식은 맞아도, 실질적으로는 패딩으로 만든 분포에 맞춰지는 현상이었다.

겪은 문제 2: 기울기가 끊겼다

임계값 기반 활성화로 논리 게이트를 흉내 내려다 기울기가 끊기고, 정확도가 3%대에서 더 이상 오르지 않았다. 미분 가능한 경로를 스스로 차단한 셈이다.

접근 방향

언어 모델에서 흔히 쓰는 요령은 모두 걷어 내고, 구조와 입력만으로 풀도록 다시 짰다.

  1. 입력 정합

곱 비트의 앞부분만 teacher 쪽에 붙이는 식으로 파이프라인을 다시 짰다. 학습 시에는 A + B + P[:-1] + [0] 형태로 넣고, 평가할 때는 A + B + [0]\*12에서 한 자리씩 채우는 방식으로 맞췄다.

  1. Weight tying

vocab이 {0,1}뿐이라 임베딩이랑 마지막 선형층 가중치를 공유했다 (fc.weight = embed.weight).

  1. 학습형 위치 인코딩

고정 sin 대신 nn.Parameter로 두니 곱셈할 때 자주 나오는 대각 정렬 같은 패턴을 스스로 잡더라.

  1. 용량 축소

d_model=16, nhead=2, layer=2, FFN 64, dropout 0, norm_first, GELU.

마스크는 미래 자리를 가리는 causal mask를 그대로 썼다.

python
1class TinyTransformer(nn.Module):
2    def __init__(self):
3        super().__init__()
4        self.d_model = 16
5        self.nhead = 2
6        self.num_layers = 2
7        self.embed = nn.Embedding(2, self.d_model)
8        self.pos_encoding = nn.Parameter(torch.randn(1, 24, self.d_model) * 0.02)
9        encoder_layer = nn.TransformerEncoderLayer(
10            d_model=self.d_model,
11            nhead=self.nhead,
12            dim_feedforward=self.d_model * 4,
13            batch_first=True,
14            norm_first=True,
15            activation="gelu",
16            dropout=0.0,
17        )
18        self.transformer = nn.TransformerEncoder(encoder_layer, num_layers=self.num_layers)
19        self.fc = nn.Linear(self.d_model, 2, bias=True)
20        self.fc.weight = self.embed.weight  # weight tying
21
22    def forward(self, x):
23        seq_len = x.size(1)
24        mask = torch.triu(
25            torch.full((seq_len, seq_len), float("-inf"), device=x.device),
26            diagonal=1,
27        )
28        x = self.embed(x) + self.pos_encoding[:, :seq_len, :]
29        x = self.transformer(x, mask=mask, is_causal=True)
30        return self.fc(x)

Day 1 숫자 (내 제출)

항목값
P1 (이론 쪽에서 주장한 하한 규모)36
P2 (학습 파라미터)6,978
Acc299.98%

P1은 AND 게이트 단위로 필요한 최소 규모를 대략 계수해 적었고, P2는 위 모델을 그대로 세어 산출한 값이다.

Day 2 — SparseTap: LPN 맥락의 LFSR·탭 복구

과제 개요

  • 길이 256 이진 시퀀스에 20% 정도 베르누이 노이즈.
  • 안 보이는 규칙은 탭 개수 S가 16 이하인 LFSR 비슷한 XOR.
  • 탭을 찾고, 주어진 64비트 시드에서 노이즈 없는 192비트를 이어 써야 한다.

손실 경치가 평탄하고 XOR는 이산적이라, 전형적인 LPN(Learning Parity with Noise) 유형에 가깝다.

시도 1: 연속 완화 + SIREN

규정상 수치 방법도 돌려야 해서 {0,1}을 {-1,1}로 바꿔 XOR를 곱으로 보이게 하고, sin(30·Wx) 쓰는 SIREN 두 층짜리 + Sign-SGD를 돌렸다.

한동안 정확도 49.8% 근처에서 그럴듯한 후보가 나와 기대했다. 그런데 노이즈가 20%라면, 규칙을 제대로 찾은 모델의 검증 정확도는 80% 근처여야 한다. 50%는 동전 던지기 수준이고, 노이즈에 맞춰진 가짜 해라고 보는 게 맞았다.

시도 2: FCA 류 무차별

GPU 기반 상관·조합 탐색은 S가 6 이하일 때는 동작했으나, 실제 탭 수는 8에 가까웠고, C(64,8) 규모에서는 GPU 메모리 한계에 부딪혔다.

최종 해법: ISD + 비트 패킹 + Numba

64개 방정식을 무작위로 뽑아 GF(2)에서 가우스 소거 → 해의 해밍 무게가 16 넘으면 버림 → 후보는 uint64로 패킹한 뒤 popcount로 앞부분만 빠르게 검증.

64개가 전부 깨끗할 확률은 대략 0.8의 64제곱이라 기대 시도는 백만 단위로 잡을 수 있다. 소거는 분기 많은 편이라 Numba JIT + CPU 한 코어가 GPU보다 낫다.

제출에 사용한 solution.py의 run_isd_ultra_fast와 동일한 구조를 아래에 요약했다.

python
1@njit
2def run_isd_ultra_fast(X_packed, Y, max_trials=5000000):
3    N = len(Y)
4    N_sub = 10000
5    X_sub_packed = X_packed[:N_sub]
6    Y_sub = Y[:N_sub]
7    for trial in range(1, max_trials + 1):
8        A_packed = np.zeros(64, dtype=np.uint64)
9        B = np.zeros(64, dtype=np.int8)
10        for i in range(64):
11            idx = np.random.randint(0, N)
12            A_packed[i], B[i] = X_packed[idx], Y[idx]
13        # GF(2) Gaussian elimination on A_packed, B
14        # drop if Hamming weight > 16
15        # pack B, verify on X_sub_packed vs Y_sub; if correct/N_sub > 0.75 return
16    return np.zeros(0, dtype=np.int8)

Day 2 숫자 (내 로그)

  • 대략 3,612,183번째 시도에서 수렴, Numba 단일 코어로 약 95초.
  • 복원 탭(1부터 세는 인덱스): [5, 14, 21, 29, 36, 42, 50, 57]
  • 검증 정확도 약 79.99% — 이론 상한 80%랑 맞는다.
  • 192비트는 제출 형식에 맞게 생성.

정리하면

  • 패딩·노이즈 패턴에 모델이 기대게 두면 채점 환경에서 바로 무너진다. 입력이 실제 연산을 강제해야 한다.
  • 큰 행렬·SIREN은 GPU, GF(2) 소거·분기는 CPU+JIT+비트가 낫다.
  • 20% 노이즈는 단순한 방해가 아니라, 후보 해를 걸러 내는 기준(약 80% 검증 정확도)으로 쓸 수 있다.

본선 진출에 이르지 못한 이유

이틀 모두 핵심 파이프라인은 동작했고, 둘째 날은 수치가 이론과도 맞췄으나 본선 컷은 넘지 못했다. 스스로 돌아보건대 원인은 다음과 같이 정리할 수 있다.

선발이 코드만으로 되지 않는다. 리포트, P1 쪽 서술, 파라미터 타이브레이커 같은 게 섞였을 가능성이 크다. P2는 꽤 줄였는데 P1 글이나 정리가 밀렸을 수도 있다.

둘째 날 시간 배분. SIREN 쪽에 시간을 썼다. 규정 충족용으로는 의미 있었지만 점수로 바로 가는 건 ISD였다. 다음엔 결정론 해부터 붙잡고 부가 실험은 나중에 할 것 같다.

총점 공식. 공개 안 된 가중치·동점 처리에서 밀렸을 수 있다. 내가 확인할 수 있는 건 로컬에서 이론이랑 맞았다는 것뿐이다.

재현성. 막판에 한 번 성공한 난수 시퀀스에 의존하기 쉽다. 여러 시드로 반복 검증하고, 최악의 실행 시간까지 README나 스크립트에 명시해 두었어야 했다.

구현이 성공해도 선발은 그걸로 끝나지 않는다는 걸 배운 이틀이었다. 다음에는 확실히 풀리는 해법부터 고정하고, 보고서는 코드와 같이 다듬고, 과제마다 시간을 미리 나눠 쓰겠다.