솔로 해커톤
부산 편 - 프로젝트 [ Ryo ] - 봇의 개발, 성능 최적화 그리고 배포
작성일: 2026-03-08
**"PyTorch는 여기서도 사고 치네..."**
처음 시작부터 PyTorch란 놈은 보안 문제를 뱉어냅니다. 그래서 그냥 처음에 해당 부분을 배제하게 만드는 코드를 배치하였습니다.
```bash
_original_load = torch.load
def _patched_load(*args, **kwargs):
kwargs['weights_only'] = False
return _original_load(*args, **kwargs)
torch.load = _patched_load
```
푸흣 발음 테스트하며 설정한 rvc의 엔진 초기호와 params 설정을 추가합니다.
```bash
rvc = RVCInference(
device="cpu",
model_path="ryo_bot.pth",
index_path="added_IVF..._v2.index"
)
rvc.set_params(
f0up_key=0,
f0method="harvest",
index_rate=0.45,
filter_radius=2
)
```
이전 글에서 명시하였지만 얘는 말을 말로 변환하는 모듈이라 타 TTS 모듈 사용이 필수입니다.
다양한 한국어 성우를 지원하는 Edge_tts 기능을 사용하였습니다.
```bash
communicate = edge_tts.Communicate(text, "ko-KR-SunHiNeural", rate="-30%", pitch="-35Hz")
```
우선 보이스 채널의 입장과 RVC 모델 정상 출력 확인을 위해 아래 슬래시 커맨드를 구현합니다.
```bash
@app_commands.command(name="말", description="료슈가 음성 채널에서 말을 합니다.")
async def speak(self, interaction: discord.Interaction, 텍스트: str):
# 1. 보이스 채널 확인
if not interaction.user.voice:
return await interaction.response.send_message("음성 채널에 먼저 입장해라.", ephemeral=True)
# 2. 추론 시간이 걸리므로 미리 '생각 중...' 상태로 전환 (타임아웃 방지)
await interaction.response.defer()
# 3. 보이스 연결 및 재생 중 확인
vc = interaction.guild.voice_client
if not vc:
vc = await interaction.user.voice.channel.connect()
if vc.is_playing():
return await interaction.followup.send("이전 음성이 아직 재생 중이다. 잠시 기다려.")
try:
# 4. 음성 생성 및 송출
audio_data = await self.generate_voice(텍스트)
source = discord.FFmpegPCMAudio(io.BytesIO(audio_data), pipe=True)
vc.play(source, after=lambda e: print(f"재생 완료: {e}") if e else None)
await interaction.followup.send(f"🗣️ **료슈**: {텍스트}")
except Exception as e:
await interaction.followup.send(f"오류 발생: {e}")
```
테스트가 성공적으로 끝나면 여타 다른 TTS 봇과 똑같이 특정 채널에서만 텍스트를 읽는 기능을 구현합니다.
```bash
@commands.Cog.listener()
async def on_message(self, message):
if message.author.bot: return
# 채널 설명(Topic)에 '료'가 포함된 경우에만 반응
if not message.channel.topic or "료" not in message.channel.topic:
return
# 커맨드는 무시
if message.content.startswith("!"): return
# 음성 채널 상태 확인
if not message.author.voice: return
user_vc = message.author.voice.channel
bot_vc = message.guild.voice_client
# 1. 봇이 음성 채널에 없는 경우 입장
if not bot_vc:
bot_vc = await user_vc.connect()
# 2. 봇이 다른 채널에 있는 경우 (이동 방지 로직)
elif bot_vc.channel != user_vc:
# 재생 중일 때는 채널을 옮기지 않음 (오류 방지)
if bot_vc.is_playing():
return
# 이동 명령이 아니면 그냥 무시 (현재 채널에 고정)
return
# 음성 재생 중이면 패스
if bot_vc.is_playing(): return
try:
audio_data = await self.generate_voice(message.content)
source = discord.FFmpegPCMAudio(io.BytesIO(audio_data), pipe=True)
bot_vc.play(source)
except Exception as e:
print(f"자동 재생 오류: {e}")
```
이제 슬슬 문제가 나올 때가 되었습니다.
코드만 읽었을 땐 전혀 문제가 없을 코드지만 프로세스의 길이로 인한 지연도 문제 중에 하나죠.
**프로세스 길이로 인한 지연 문제**
tempfile를 활용하여 /dev/shm 영역에 wav(생성된 보이스 파일) 저장 구현으로 데이터 이동에 대한 프로세스를 최적화합니다.
```bash
import tempfile
# generate_voice 함수 내부 수정
# /dev/shm은 RAM을 직접 하드디스크처럼 쓰는 공간입니다.
temp_dir = "/dev/shm"
with tempfile.NamedTemporaryFile(suffix=".wav", dir=temp_dir, delete=False) as t_in, \
tempfile.NamedTemporaryFile(suffix=".wav", dir=temp_dir, delete=False) as t_out:
in_p, out_p = t_in.name, t_out.name
```
OS environ 지정을 통해 코어를 할당합니다.
```bash
#python
os.environ["OMP_NUM_THREADS"] = "6"
os.environ["MKL_NUM_THREADS"] = "6"
import torch
# 설정이 잘 되었는지 확인하는 법
print(f"현재 설정된 스레드 수: {torch.get_num_threads()}")
#Dockerfile 구성
ENV OMP_NUM_THREADS=6
ENV MKL_NUM_THREADS=6
```
기존에 쓰던 봇이 전부 출력하고 나서야 시작을 하는 수준이지만, 이전 출력 이후 수초 후에 프로세스 완료 및 출력 보단 확실히 개선된 성능을 볼 수 있었습니다.
**K3s 클러스터 안착 중 **~~**억까 아니**~~** 오류 발생**
Docker Build와 save 과정 이후 생성된 tar 파일의 용량은 4.7GB 나 되었습니다.
이를 쿠버네티스에 올리고 나서 없던 오류가 하나 생기더군요. bot_vc 함수가 초기화되지 않는 문제였어요. ~~이 색히가~~ 초기화가 되지 않아..? 근데 이미 제 코드에선 아래처럼 이미 선언된 상태였거든요.
None 인지 아닌지를 구분하는 if/elif 구문에서 발생하고 있었어요. 초기화가 되지 않아 구분을 할 수가 없다고.
그래서 그냥 실시간 채팅 감지 로직에 봇을 걸러내는 코드 아래 바로 생성해 주었습니다.
```python
# --- 실시간 채팅 감지 로직 ---
@commands.Cog.listener()
async def on_message(self, message):
if message.author.bot: return
bot_vc = None
# 채널 설명(Topic)에 '료'가 포함된 경우에만 반응
if not message.channel.topic or "료" not in message.channel.topic:
return
```
이 상태로 해결이 되었다는 게 어이가 없더군요. 컨테이너 환경으로 옮기면서 발생한 미묘한 인터프리팅 차이 또는 비동기 타이밍 이슈로 인한 오류로 보는 것이 좋을 것 같습니다.
또 다른 문제로는 K3s 이미지가 빌드를 다시 하고 주입해도 업데이트되지 않는 문제가 있었는데 이건 캐시 문제로 버전을 다시 주입하여 배포, 이미지 파일 이동, 쿠버네티스 내 실행 과정을 다시 해주어서 해결하였습니다.
다행히도 재배포를 통해 업데이트된 코드를 실행할 수 있었습니다.
**첫 솔로 해커톤을 마무리하며...**
다음 해커톤 주제는 Ether RPG 직업, 스킬, 강화 시스템 구축일 텐데 2박은 어림없고 4박 이상은 잡아야 할지도 모르겠다는 생각이 듭니다.
이런 생각은 나중으로 미루고, 부산 편은 솔직하게 앞으로의 제 커리어와도 연결되는 사이드 프로젝트였습니다.
20살이 되던 해 3월부터 지금까지 3년이라는 기간 동안 하드웨어 조립/세팅/QC 분야에서 근무하다 그동안 준비했던 개발 분야로의 전환을 준비하는 기간이기도 했습니다.
이번 해커톤을 통해 코드만을 구축하는 것이 아닌 AI 모델 내부에 대해서 공부하고, 컨테이너 내에서 일어날 수 있는 적지만 몇 가지의 문제를 맞닥드리고, 이를 어떻게 해결해야 할지에 대해 생각할 수 있는 엔지니어로 한 단계 성장했을 거라 믿습니다.