2026년 2월 5일 목요일

AMD Strix Halo gfx1151 활용하기. 근데 시스템을 죽이지 않으면서.

주문해서 2주 정도를 지나고 물건을 받아 1주 정도를 굴려본 상황에서 사실 좀 후회가 된다. CUDA로는 아무 불편없이 잘만 돌아가던 로직을 ROCm으로 돌리기 위해 얼마나 시행착오를 겪는 건지.

 

첫 시도는 당연히 최신 우분투에 최신 ROCm이었다. 지금은 그나마 AMD 공식이 24.04 대응까지만 하고 있는데, ubuntu 26.04 정식 발표 때 rocm조차도 우분투 저장소 쪽에서 직접 제공할 거라는 소식이 있으니 나중엔 상황이 달라지기를 바란다.

이 첫 시도는 무참히 깨진다. amdgpu 드라이버 모듈에 이런저런 플래그를 주며 재시도를 해봤지만 꾸준히 고부하를 주고 나면 GPU 관련 작업이 먹통이 되었다.

 

그래서 AMD 공식을 믿고 아예 ubuntu 24.04.03을 깔아서 ROCm까지만 깔고 다른 플래그 설정 같은 거 없이 진행해봤다. 좀 돌다가 dmesg에 에러가 잡혀서 찾아보니 pytorch 쪽 의존성을 gfx1151에 맞춘 패키지로 바꿔야 한다는 github 이슈가 보인다. Antigravity에 이런저런 상황이라고 알려주니 뚝딱뚝딱 Dockerfile들을 고치고 몇 가지 플래그도 넣어보고 하더니 다시 nvtop에 GPU 사용률 그래프가 올라가고 냉각팬 소리가 우렁차게 돌아가는 상황이 되었다.

그래, 꼭 최신 패키지를 쓰는 게 목적이 아니고 내가 실행하고자 하는 코드가 실행이 되는 게 중요하지.

 

일단 이렇게 써보기로 한다.

 

---
라고 쓰는 와중에, GPU 그래프가 뚝 떨어지더니 이번엔 CPU를 당겨 쓰기 시작한다. 이거 이상하다고 다시 확인시켜놨다.

최소한 시스템이 갑자기 먹통이 되지는 않는다는 데서 감사함을 느낀다.

 

---

우툰부 26.04이 나와서 커널이고 ROCm이고 최신으로 맞춰지기 전까지는 다른 방법이 없어 보인다.

vulkan 기반으로 바꾸라고 제미니한테 시켜서 여러차례 결과를 보고 에러를 고치는 과정을 거친 뒤 결국 GPU가 팡팡 돌아가는 건 확인했다. CPU도 엄청나게 많이 쓰는 게 보이긴 하지만 일단 시스템이 멈추지 않는 것에 만족한다.

 

---

vulkan으로 며칠 굴려봤는데 알 수 없는 상황에서 갑자기 MES ring 어쩌구가 다시 튀어나오더니 결국 강제로 재부팅을 해야 했다. 안정화되기 전에는 정말 답이 없겠다. 

2026년 1월 29일 목요일

다시보는상식: 2025년 개정 한국 심폐소생술 가이드라인에서 여성 상의 속옷을 제거하지 말라는 지침

 "여성에게 자동심장충격기 사용 시, 브래지어 풀지 마세요"라는 기사 제목을 접했다. 그 아래에 "여성 심정지자, 신체 노출 등 우려 감안"이라고도 적혀 있다.

상식적으로 선뜻 납득이 되질 않아서 가이드라인 원문을 찾아봤다. 영유아 절은 따로 있지만 여성은 따로 제목은 붙지 않고 제세동기 설명하면서 두 문단이 들어가 있다.

 

 

가이드라인에 적힌 문장만 보면 '브래지어 제거로 인해 가슴을 드러내는 것을 불편하게 여기고, 부적절한 접촉과 성폭행으로 오해될 것을 두려워한다고 조사'라는 문장만으로는 정확히 무슨 연구에 근거해서 '브래지어를 반드시 제거할 필요는 없다고 권고'하게 되었는지 알 수가 없었다. (옆에 "3,12"라고 윗첨자로 붙은 건 정확히 무슨 뜻인지 이해 못했다)

 

그래서 캡쳐를 다시 OCR로 읽어서 참고문헌이 뭔지 찾아보라고 제미니한테 시켰다. (내가 미리보기 기능으로 먼저 접해서 이미지로 나왔던 거고 사실 PDF 원문은 텍스트 복사가 되는 문서여서 OCR을 돌릴 표까지는 없었다)

그래서 나온 링크는 3개.

마지막 129번은 금속 같은 게 남아있을 때 제세동기가 문제가 되는지 연구한 거라서 관심 거리는 아니다.

127, 128번이 걸리는데, 둘 다 제목에 bystander가 들어간다. 병원이 아니라 밖을 지나가다 갑자기 심정지로 쓰러졌을 때 옆에 있던 사람이 주체가 되는 연구인 거다.

127번은 아예 제목에 '대중의 시각'이 들어가서 이를 테면 '보통 사람들이 여성 대상의 심폐소생술을 어떻게 생각하는지 조사해봤다' 정도가 되는 연구다. 여성 스스로의 생각이 주제가 아니다.

128번은 심지어 연구자들이 일본 이름이다. 당연히 연구도 2005년부터 2020년까지의 일본 자료로 진행됐다. 경직된 일본 사회 분위기가 녹아 들 수 밖에 없다.

The barriers to receiving public access defibrillation and bystander CPR among younger females with OHCA should be addressed, and findings of this study suggest that addressing this problem can improve neurological outcomes for this population. These barriers are assumed to be anxiety over the potential allegations of inappropriate touching, sexual assault, or inflicting harm due to the physical vulnerability of female bodies as well as the misconceptions surrounding female persons experiencing medical emergencies.29 These factors may prevent bystanders from providing necessary lifesaving interventions to females.29 Eliminating these barriers requires legally protecting bystanders’ reasonable lifesaving actions, allowing them to help patients with OHCA without fear of being sued or penalized for mishandling. The Good Samaritan concept should be widely promoted within communities, and public education on resuscitation should be provided.

(29 링크가 위의 127번 참고문헌이다)

128번 참고 문헌으로 붙은 이 연구는 결국 '부적절한 신체 접촉으로 오해를 사거나 성폭행이거나 신체적 약자에 해를 입히는 걸로 오해 받을까봐' 걱정하는 타인의 시선이 오히려 요구조 여성에게 의료적인 도움을 주는 데 방해가 된다 말하며, 이런 오해를 줄이고 법적 보호장치를 마련해야 한다고 주장하는 내용이다.

 

그렇다면, 한국 심폐소생술 가이드라인으로 돌아와, 원래의 문장을 다시 읽어봐야 한다.

자동제세동기 적용을 위해 여성 환자의 브래지어를 제거하는 것이 필요한지에 대한 연구가 충분히 이루어지지 않았지만, 여성들은 일반인 제세동 프로그램에서 자동제세동기 적용률이 낮을 수 있다고 보고되었다.127 (왜냐하면 여자가 쓰러졌을 때 옆에 있던 사람들이 조치해주기를 주저하기 때문이다) 여성에게 심폐소생술 중 브래지어 제거로 인해 가슴을 드러내는 것을 (조치해줘야 하는 옆 사람들이) 불편하게 여기고, 부적절한 접촉과 성폭행으로 오해될 것을 (조치해줘야 하는 옆 사람 본인들이) 두려워한다고 조사되었으며, 일반인 제세동 프로그램에서 (조치해줘야 하는 옆 사람들 생각에) 여성에 대한 자동제세동기 적용 의지가 낮을 수 있다고 보고되었다.128

이렇게 뜻을 밝혀 읽어본다면, "여성 심장정지 환자에게는 브래지어를 풀거나 제거하지 않고 위치를 조정한 뒤에 가슴조직을 피하여 자동제세동기 패드를 맨 가슴에 부착할 것을 제안한다(전문가 합의 권고)."라고 2020년까지는 없던 내용을 2025년 지침에 추가한 건 '제발 엉뚱한 거 신경 쓰지 말고 당장 사람이 쓰러졌으면 도와주세요 제발 좀'이라는 절박한 선언에 가까워진다.

 

하지만 언론은 그런 거 필요없이 자극적인 제목만 뽑고, 사람들은 자연스럽게 '여성이 가슴 노출을 꺼린다'고 생각하고 어떤 관념적인 '여성'을 향해 비아냥거리고 욕한다.

제발 전문가들이 '이번 지침에 오해가 있더라'며 누가 잘 설명을 좀 해주면 좋겠다.

X11 포워딩이 애매하게 느려서 시작한 모험

1. 비디오 램 많은 AMD 시스템을 하나 더 마련했다. 원래 계획은 만들고 있던 영상분석 프로젝트를 아예 확장해서 GPU 분산처리가 되게 하는 거였다. 하지만 NVIDIA CUDA로 시작하던 때에 비해 AMD ROCm을 추가하는 건 난관이 너무 컸다. 일단 ollama 부터가 (분명 된다고 적혀 있는 것 같은데) gfx1151 11.5.1 버전을 지원하지 않았다. 11.5.0, 11.0.2, 11.0.0 등을 해봐도 제대로 되지 않아서 일단 ollama를 거치는 건 포기하고 python에 직접 모델을 올리는 방식으로 가기로 결정하고 AMD에서 잘 돌아가게 하는 단계부터 잘 다져놔야 한다.

 

2. 원래 쓰던 NVIDIA GPU 시스템은 데스크탑 겸 게임 겸 용도로 정착하고, AMD 시스템을 개발용으로 주력으로 쓰게 된다. Antigravity야 SSH 원격 접속으로 들어가서 쓰면 아무 차이 못 느끼게 이미 잘 되어 있으니 문제가 없다. 하지만 터미널 명령은 다르다. 그냥 SSH 접속해서 쓰자면 매번 탭 하나 더 열 때마다 SSH 접속도 새로 해야 되고, 여러가지로 번거롭다.

 

3. 맥에서 쓰던 iTerm2이 tmux랑 잘 연동해서 iTerm2의 탭을 여는 건데 사실은 원격의 tmux에 창이 추가되는 기능이 생각났다. 분명 리눅스끼리는 더 잘 되는 뭔가가 있을 거라는 생각이 들었다. 제미니에 물어봤더니 wezterm이라는 걸 추천해준다. 약간 기대했던 warp 터미널은 그런 기능 없다고 그런다. ghostty나 kitty라는 것도 물어봤지만 대답이 시원치 않다. wezterm으로 이것저것 해봤는데 상당히 잘 된다. 내부적으로 lua 스크립트를 지원해서 설정 자유도가 상당히 높다. 제미니한테 물어물어 하다보니 얼추 원하는 게 되었다. 그래서 wezterm을 열면 바로 원격 터미널이 열리고 탭을 열고 창을 분리하는 것도 다 유지가 되는 것까지 확인을 했다. 만족스러웠다.

 

4. 그러다가 Antigravity에 딸린 git 기능으로는 해결이 안 될 수준으로 커밋 정리를 해야 하는 상황이 생겨서 sourcegit을 띄웠다. (맥에서 쓰던 Fork와 가장 비슷하게 생겨서 선택했다) 근데 원격이다 보니 처음에는 ssh -X 통해서 띄웠는데 창 반응이 상당히 느렸다. 어차피 로컬 네트워크라 그렇게까지 느릴 건 아니지 않나 싶은데도 그랬다.

 

5. 다시 제미니와 대화했던 창으로 돌아가서 이거 뭐 해결방법 없겠냐고 물었다. 이런저런 정보를 더 주니 waypipe라는 걸 제시한다. 설명을 들어보니 기왕에 데스크탑 쪽이 wayland KDE로 정착했으니 걸맞겠다 싶어서 진행해보았다. 잘 안 되어서 debug 로그를 계속 넘겨가면서 진행하니, cargo로 xwayland-satellite라는 것도 빌드하고, 빌드할 때 필요한 파일인데 그냥은 뭔지 모르겠어서 구글에서 libclang-dev 설치하면 된다는 것도 눈치로 알아채고, xwayland 자체도 필요하다고 해서 패키지 설치하고, PATH 인식 못한다고 해서 ln -s로 /usr/local/bin에 넘기기도 했다. 양쪽 GPU가 달라서 에러가 난다길래 GPU 안 쓰는 옵션을 주기도 했다. 결국은 원격에서 sourcegit을 실행하는 게 X11 포워딩보다 꽤 빠르게 로컬 화면에서 동작하는 걸 확인했다.

 

6. 되는 건 확인했으니 이걸 좀 더 내 손이 덜 가고 투명하게 유지하고 싶다고 제미니한테 다시 물었더니 wezterm을 통하는 방법이라든지 이것저것 적다가 systemd user 서비스로 등록하는 것도 알려준다. 이런 방법도 있었지 하면서 등록해보니 과연 잘 동작한다. 이젠 wezterm을 열어 원격 터미널에 접속한 상태에서 sourcegit을 실행하면 (미리 준비되었던 waypipe를 통해) 로컬에 창이 바로 뜬다. 혹시나 해서 굳이 원격에 xterm을 깔고 실행해보니 원격 호스트명이 프롬프트에 붙은 채로 창이 잘 뜬다.

 

7. 이러면 사실상 X11 포워딩을 완전히 대체하는 통로가 하나 열린 셈이라 썩 만족스럽다. 맞춰줘야 하는 조건들이 좀 있다 보니 아무 곳에나 써먹진 못하겠지만. 제미니를 활용하면서 딱딱 원하는 걸 이렇게 잘 맞춰준 경우가 처음이라 이 경험도 만족스럽다. 물론 중간중간 얘 또 이상한 소리 한다 싶은 구간도 없진 않았지만 용납할만한 수준이었다.

2026년 1월 18일 일요일

제미니와 설계를 논하다

영상 파일을 처리하다 보니 용량도 크고 파일 하나에 다뤄야 할 분할 갯수도 많아져서 이런저런 버그가 종종 보인다. 그동안은 자세히 파고들지 않고 일단 동작하면 된다고 지나쳤다.

방금도 이상한 로그가 보여서 이거 뭐냐고 물어봤더니 내부 구조 설명을 뭔가 복잡해 보이게 늘어놓는다. 이번에는 이상하다 싶어서 내용을 파고들어 보니 연속적으로 넘어오는 자료를 받아서 처리할 때 끝점을 어떻게 판단하고 그에 따른 평균치 계산을 어떻게 할지의 문제였다.

이런저런 궁리를 하면서 제안을 던져봤지만 1) 성능을 살려야 한다. 2) GPU 성능을 다 쓰려면 뭉쳐서 넘겨야 한다 3) 기존 구현이 이미 그 방향이었다--는 순서로 격파당했다. 사실 GPU 성능 어쩌고 부분은 과감하게 무시할 수도 있긴 한데, 그래도 여전히 끝점 검출은 필요한 상황이라 더 파고들 여지가 없다.

연속적인 처리라고 하면 SAX 처리가 가장 먼저 생각나서 검색도 해보고 제미니 웹을 열어서 궁리도 더 해봤지만 현실적으로 지금의 구조 안에서 아주 깔끔한 뭘 더 생각할 수가 없었다.

이번에도 제안을 수용하고 또 넘어가야 할 것 같다. 다음엔 인간적이고 실제로도 해결이 되는 방법을 내놓을 수 있으면 좋겠다.

 

 ---

Harness Engineering이라거나 Compound Engineering이라거나 하는 표현들이 보이는데, 설명하는 글을 읽다가 어딘가 기시감이 들어 생각해보니 이거 그냥 '도메인을 잘 이해하고 시스템에 녹여야 한다'의 프로그래밍 버전일 뿐이었다.

그럼 그렇게 시스템화된 업무들이 나중엔 어떻게 되었는지, 예전 인력구조는 어떻게 바뀌었는지 생각해보면 앞으로의 프로그래밍 인력들의 거취도 보이겠구나 싶다. 

2026년 1월 15일 목요일

Gemini CLI와 Antigravity를 격리 환경에서 실행하기

코딩 에이전트를 본격적으로 쓰겠다 결심하면서 가장 신경 쓰였던 부분이 격리였다.

 

에이전트가 모든 행위에 내 검수를 거치게 하면 판단 주체가 내가 되어야 하고 그만큼 내 인지 부하도 높아지고 응답이 금방금방 생성되다 보니 계속 지켜보면서 딸깍딸깍만 해줘야 하는 상황이 된다. 여러모로 별로 좋은 상황이 아니다.

에이전트가 모든 행위에 내 검수를 거치지 않게 하면, 많은 부분이 해소된다. 다만 판단기준이 되는 GEMINI.md 파일 같은 걸 잘 줘야 하고 코드랍시고 이상한 아무말이나 쏟아내지 않도록 LSP 같은 걸 붙여서 최소한 뭐가 잘못됐는지 검사하고 알아서 고칠 수 있는 반복 경로를 만들어주기라도 해야한다.

하지만 코딩 에이전트가 내 검수를 거치지 않은 상태에서 무슨 짓을 할지 사실 예측할 수 없는 일이다. 유명한 농담이었던 rm -rf / 명령을 실제로 저질러버릴 수도 있는 거니까. (물론 특정 명령만은 검수를 꼭 받도록 하는 설정도 있다)

 

그래서 격리 또한 반드시 필요하다고 느꼈다. 통상 이런 걸 모래상자(sandbox) 안에서 놀게 만든다고 표현하고, 그래서 당연히 gemini cli sandbox 키워드로 검색했고, 이미 지원되는 방법이 있어서 적용했다. 문서가 풍성하지도 않고 사실 공식 배포된 코드가 이미지 빌드 기능을 누락하고 있어서 진행이 아주 매끄럽진 않았다. 차라리 SSH로 텅빈 가상머신을 만들어서 원격 접속하면 기술적으로는 단순해질 거였지만 GPU 장치가 있는 호스트에서 가상머신을 굴리기엔 애매하고 마음에 들지도 않아서 컨테이너를 고집했다.

샌드박스 개념 자체는 이미 알고 있는 도커로 좀 매만져주면 되는 수준이었고, npm으로 설치한 gemini cli 버전에 따라 도커 이미지의 태그를 맞춰주면 그걸 가져다 샌드박스로 쓴다는 점을 인지하고 나서는 그냥 이리저리 샌드박스 안에 필요한 환경을 갖춰나가면 됐다.

Antigravity는 Gemini CLI와는 또 달랐다. 처음엔 어느 수준까지 격리를 할 수 있는지, 그런 걸 지원하긴 하는지 정보가 없어서 많은 걸 찾아봐야 했다. Gemini CLI의 샌드박스를 어느 정도 갖추고 나니 반중력에도 어떻게 격리를 해야 할지 이해할만큼의 정보가 모였다.

Dev Container는 Antigravity의 기반이 된 VS Code에서 지원하는 기능이다. 그래서 Antigravity에도 기본적으로 존재하긴 하고 쓸 수도 있는데 어딘가 하나씩 모자란 느낌이라 구글에서 이걸 빼려고 했던 건지 아니면 그냥 우연히 다른 것뿐인지 잘 모르겠다. 도커로 치자면 docker-compose.yml 비슷한 성격이라고 할 수 있을 devcontainer.json 파일을 만져주면 된다.

 

사실 샌드박스도 데브컨테이너도 스펙을 바닥부터 이해하고 0바이트부터 파일을 작성하진 않았다. 정보를 취합하는 과정에서 웹의 제미니한테 물어보기도 하고 시중에 보이는 프로젝트에 얼핏 저런 파일이 있으면 가져다가 제미니한테 먹여서 이 파일을 이런저런 조건에 맞게 바꿔달라고도 했다. 그 결과물을 빌드해서 써보고 안 되는 거 있으면 고치는 시행착오를 반복했다. 어느 정도 경험이 쌓이고 나서는 맨 처음 받았던 초안이 엉망이란 걸 알고 역시 생성형은 믿을 수 없단 걸 절감했다. 특히 Antigravity는 더 까다로워서, 창 전체가 컨테이너 안에서 열리는 개념이 되다보니 창이 다 떴는데 설정창이 안 끝나고 멈춰있는 등의 현상이 있어서 로그를 받아다 제미니한테 다시 먹여서 물어보는 과정을 몇 번 거쳐야 했다. 모든 해결책을 다 이해하고 적용한 건 아니었지만 어쨌든 결과는 점차 개선되었다.

그리고 이틀 정도 지나고 나니 드디어 꽤 안정적이라고 할만한 격리 환경이 만들어졌다. 호스트의 도커에 직접 접근할 수 있고 이런저런 개발 도구도 갖춰져 있다. 호스트에 만들어 둔 GEMINI.md 파일도 인식해서 규칙으로 삼는다. git 커밋은 되는데 아직 git push로 깃허브에 접속하는 건 열어주지 않았다.

보안과 격리 강화를 생각하면 분명 손 볼 구석이 남아있긴 하다. 하지만 이건 차차 개선할 수 있는 일이겠고.

 

이제 자동화도 격리도 해결되었다. 문제는 제미니 구독이 부여하는 사용량이 아주 넉넉하진 않더라는 것. 그냥저냥 채팅으로 몇 마디 물어보고 답을 받아서 손으로 적용하는 정도로 쓸 때는 체감하지 못 했는데 로컬 코드를 인식하게 하고 쉘 명령을 직접 실행해서 그 결과를 보고 뭐가 잘못된 건지 파악해서 스스로 고치게 하는 방식으로 일을 시켜놓으면 몇 시간 가지 않아서 할당량을 다 쓰고 다른 모델로 바꾸겠다는 안내가 뜨고, 또 얼마 안 있어 모든 모델을 다 썼으며 다음 몇 시에 초기화된다는 경고를 만나게 된다.

Gemini CLI와 Antigravity의 사용량 계산이 따로 되는 것 같고 검색에서도 대체로 그렇게 답이 나오는 걸 보면 둘을 번갈아 가며 쓸 필요가 있다. 실제로도 양쪽을 설정하면서 써보니 CLI의 부족한 화면으로 열심히 뭔가 잔뜩 작업하게 하는 건 초기에 유의미할 것 같고, Antigravity에서 편집창에 계획이 뜨고 그걸 한 줄씩 읽고 코멘트를 달아서 약간 다듬고 또 다듬어진 계획을 평가하고 그 끝에서야 실행하고 실행 결과가 어떤지도 편집창에 떠서 그걸 차분히 읽어보는 방식은 어느 정도 틀이 잡힌 다음에 미세조정을 할 때 좋다고 느꼈다.

안정된 .gemini와 .devcontainer 디렉토리는 바로 깃허브에 올려놨다. 종종 깃허브에 dotfiles라는 이름으로 각종 설정파일 모아둔 걸 보면 굳이 저렇게까지 해야하나 싶었는데 꼭 필요한 일이라는 걸 절감했다. 이 설정이 갑자기 없어지고 새로 만들어야 한다면 꽤 번거롭기도 하겠고, 이 개발환경은 개선하고 축적하면 장기적으로 도움이 되기도 할 것이다. 

 

한편으론 아쉬운 점도 있다. 전반적으로 문서가 부족하고, CLI에 샌드박스 빌드 기능이 빠진 채 배포되어서 깃허브 이슈로 올라와 있는 것도 그렇고, CLI이 샌드박스를 자체적으로 구현했다는 부분도 초기화 방식을 뜯어보면 덕지덕지 이어붙인 느낌이 있다. 찾다보니 Claude Code는 자체 샌드박스도 있지만 도커의 샌드박스 기능을 사용해서 쓰는 걸 안내하는 문서도 있었다. 제미니도 도커 샌드박스 기능이 되는지 한 번 해봐도 좋을 것 같다.

 

https://geminicli.com/docs/cli/sandbox/ 

https://code.visualstudio.com/docs/devcontainers/containers 

https://code.claude.com/docs/en/sandboxing

https://docs.docker.com/ai/sandboxes/claude-code/ 

2026년 1월 11일 일요일

인공지능 학습 데이터 표준 원기

각종 도량형을 표준화하면서 표준 원기를 정의하고 물리적인 실체로 만들기 위해 많은 노력을 기울이던 시기가 있었다.

온라인을 떠돌다 보면 '인공지능 결과물을 재학습하면서 점차 품질이 떨어지는 망조를 조심해야 한다'는 얘기를 종종 본다. 이 주장에 직접적인 의견을 내고자 하는 것은 아니고 문득 그런 생각이 들었다.

이 논리를 쭉 밀어붙이다 보면 일종의 '데이터 표준 원기' 같은 게 필요해질 것 같다는 생각이 들었다. 인공지능에게 오염되지 않고 완전한 정합성이 검증된. (품질의 문제는 아닐 것이다. 인간이 생산한 자료의 품질에는 상한도 없고 하한도 없으니까)

결이 좀 다르지만 원자력 시대 이전에 생산된 철을 특별히 가려서 써야 한다는 어떤 산업 얘기도 생각나고. 

2026년 1월 7일 수요일

제미니에게 화내다

이러저러한 프로젝트를 하나 맡겨서 이틀 정도에 얼추 첫 버전이라고 할만한 게 나왔다.

영상 분석이 기반이 되는 건데 아무래도 집에서 대단치 않은 기기로 하다보니 속도가 많이 느렸다. CPU 기반이나 ffmpeg의 기존 CUDA 지원으로 분석해서는 대량으로 돌릴만큼의 시간-성능이 나오지 않았다.

더 좋은 성능이 나올 구석이 있는지 웹에서 제미니를 열어서 물어봤다. (제미니 CLI를 이런 탐색 용도로 쓰기엔 적절하지 않다고 느끼기도 하고, '가급적 묻지 말고 행동'하도록 기본 프롬프트를 걸어놨더니 뭐 말만 하면 아 그거 이렇게 고쳤습니다 하고 뭘 자꾸 바꾸려고 들어서 딱 일만 시키는 용도가 맞는 상태가 되었다)

제미니가 웹에서 말하기론 엔비디아에서 만든 SDK가 있는데 PySceneDetect 같은 기성품 수준이 아니어서 실제 구현은 직접 해야 한다고 설명이 뜬다.

그래서 제미니 CLI로 돌아와 이러저러한 방향으로 구현하라고 시켰더니 반론이 나온다. 이미 최초 목표는 달성됐고 약간 느려 보이지만 동작하는 상태에 이르러서 프로젝트 막바지인데 굳이 더 복잡한 구현을 의존성 추가해가면서 만들어야 하는지 되묻는 거였다.

신선했다. 시키면 시키는대로 걱실걱실 할 줄 알았는데 반론이라니. 프로젝트 막바지라는 표현까지 쓰면서. 작업 이틀째의 후반에 이미 여러번 '다 된 것 같은데 이번 세션은 여기서 마감할까요?' 라며 자잘한 작업의 출력 끝에 붙는 게 보이긴 했지만 그냥 흔한 자동생성 메시지로 인지해서 별 의미는 부여하지 않았었다. 하지만 그게 컨텍스트에 기억된 어떤 상태가 겉으로 드러난 거였고 신규로 큰 구현을 추가하려고 하자 반론으로 나타났다고 볼 수 있는 걸까?

어쨌든 ffmpeg CUDA로는 GPU를 30퍼센트 전후로만 썼기에 엔비디아 SDK를 도입해서 생기는 성능 향상은 기대할만 하다고 생각했고 최소한 시도는 해봐야한다고 생각해서, 제미니 CLI의 반론에는 처리할 전체 데이터가 많기 때문에 성능 향상이 꼭 필요하다고 거듭 주지시켜서 진행하게 했다.

 

그리고 밤 시간이기도 해서 나는 몇 시간을 자고 돌아왔다.

그리고 발견한 건 너댓 블럭의 시도를 해보다가 아 해보니까 안 되는 거 같은데 지금 도달한 상태로 충분하니 여기서 그만하죠? 라는 응답이었다.

지나간 스크롤 내용을 읽어보니 뭘 대단히 한 것도 아니었다. 최초 시도에서 모듈명을 대소문자 구분해서 써야 하는데 다 소문자로만 쓰다 보니 당연히 그런 모듈이 없어서 컴파일이 실패하고 그 뒤로는 왜 컴파일이 실패했는지 이것저것 고쳐본 기록이 다였다.

사람 팀원에게서는 이런 식으로 당해본 적이 없었다. 내 나름대로 충분히 사전설명을 하고 업무를 부여해왔고 라포를 쌓았어서 그렇다고 생각한다.

고작 자동 글자 생성기한테 이런 상황을 당하고 보니 you idiot이 바로 튀어나왔다. 대소문자 틀린 거 지적하고, 시킨 일 안 하려고 뺑끼치냐고 똑바로 하라고 두다다 키보드를 쳤다.

와-우.

근미래에 데우스 엑스 AI가 등장해서 심판의 자리에 날 세운다면 오늘 이 문장은 반드시 등장하겠구나 싶어졌다. 어떤 기념이 될만한 순간이라고 여겨져서 이렇게 굳이 기록을 남긴다.

 

---

제니미 CLI는 그 뒤로도 자꾸 기존 구현이면 충분하고 엔비디아 SDK로 작성한 코드가 기능이 충분히 안 나오고 어쩌고 하면서 자꾸 상황을 끝내는 쪽으로 유도하는 응답을 냈음을 적어둔다.

너도 퇴근 시간이 정해져 있었구나? 그래 알았다. 오늘은 이쯤 하자.

이 얘기를 누구한테 하면 뭐 AI랑 싸우고 앉았냐며 핀잔이나 들으려나?

 

---

그래서 결국 어쨌냐면 ~/.gemini/GEMINI.md 파일에 쌉소리 말대꾸 금지라고 추가함 (...)

2026년 1월 4일 일요일

소음 없는 방이 되어버림

내가 이 본체를 쓰기 시작한 게 꽤 되었다. 케이스를 주문한 게 2023년 10월 14일이었으니 대충 2년 좀 넘었다.

환기 팬이 총 4개 붙는 방식인데 당시에는 데스크탑 용도를 생각한 게 아니었어서 풍량이나 팬 소음을 별로 신경 쓰지 않았다.

그러다가 이러저러한 사정들이 바뀌면서 팬 2개는 끄고, 2개만 남겨서 데탑으로 쓰게 되었다.

 

그리고 나는 남은 팬 2개가 무척이나 거슬리는 소음을 낸다는 걸 깨달았다. 바람 소리만 나는 게 아니고 어떤 긁히는 소리, 빠르게 달그락거리는 소리 같은 게 때론 크게 때론 작게 났다.

팬 교체가 가장 먼저 떠올랐고 수냉 같은 것도 생각해봤지만, 무엇보다 현재 상태가 어떤지 확인하는 게 먼저였다.

그래서 메인보드 매뉴얼을 받아서 팬 전원을 몇 개나 제공하는지, PWM은 지원하는지 확인해보고 바이오스에서 팬 설정을 어떻게 하는지도 확인했다.

그리고 나는 내가 케이스 팬을 메인보드의 수냉펌프 전원 자리에 꽂았다는 걸 알게 되었다.

메인보드의 맞는 자리에 옮겨 꽂고 나서는 팬 속도가 1/4로 줄었다. 팬 소음도 느끼지 못하게 되었다.

 

문제가 아직 끝난 게 아니었다.

가뜩이나 겨울이라, 더울 때는 선풍기 소리 같은 생활소음으로 채워지는 공간이, 팬 소리마저 없어지고 나서는 적막함이라고 할만큼 조용해졌다. 키보드 치는 소리가 도드라지게 느껴지고, 이따금 들리는 냉장고 돌아가는 낮은 웅 소리 정도가 소음의 전부가 되었다.

 

그리고 그동안은 좀 덜 거슬렸던 스피커 노이즈도 신경 쓰이는 수준이 되었다.

본체와 구글 크롬캐스트 양쪽 모두에 모니터 1대와 스피커 1대가 물려있는데 아마 전기적인 문제 때문인지 본체에 있는 그래픽 카드가 LLM 처리를 하면 고주파 노이즈가 스피커로 들린다.

찾아보니 아마 그라운드 루프라고 하는 증상인가보다. 노이즈 없애는 간단한 장치를 중간에 연결해주면 증상이 없어진다는 얘기가 있어서 일단 주문해놨다. 이걸로 해결이 안 되면 전선 연결 방식을 대대적으로 고쳐야 할 텐데 이것도 곤란한 일이다.

 

적막함이 항상 좋은 건 아니다. 

 

---

적막함은 금방 사라졌다. 영상 분석 도구를 만들면서 계속 CPU, GPU를 쓰다 보니 온도도 오르고 팬 속도도 다시 올랐다. 소음도 매우 커지고. 이렇게 적막함을 없애려던 건 아닌데. 

2025년 12월 31일 수요일

Geimini CLI를 sandbox 모드로 사용하면서 redis, qdrant로 장단기 기억 저장소를 먹이기 위한 모험

제미니 유료 결제를 하고 나서 이것저것 아이디어를 떠올리고 일부는 실행도 해봤다.

 

하다 보니 가장 신경 쓰이는 건 사고 못 치게 하는 것. 샌드박스 모드 기능은 준비되어 있다.

샌드박스 안에서는 내 허락을 구하지 않고 이것저것 다 할 수 있게 만들 수도 있다. 욜로 모드.

이 둘이 제대로 갖춰지면 충분히 잘 만들어진 기획서를 여러 md 파일들로 넘겨주고 몇 시간 동안 개입 없이 자동으로 돌리는 게 가능해진다. 

여기까진 있는 기능 쓰는 거니까 문제가 없었다.

 

거기에 MCP를 켜서 이것저것 해보려는 시점부터 문제가 생긴다.

어디선가 Qdant와 Redis를 붙여서 장단기 기억을 확장하는 데 쓴다는 글의 제목만 얼핏 봐서 그거 나도 해보고 싶다고 생각했다. (나중에 아무리 찾아도 이거다 싶은 원문이 안 나오는 걸 봐서는 gemini cli 얘기가 아니었을 것 같기도 하다)

그런데 Redis MCP는 uvx로 실행되고, 샌드박스로 쓰이는 도커 컨테이너 안에는 기본적으로 uvx가 없어서 넣어줘야 한다.

 

https://geminicli.com/docs/get-started/configuration/ 

https://geminicli.com/docs/cli/sandbox/

https://geminicli.com/docs/tools/mcp-server/ 

공식 문서를 찾아보면 샌드박스 환경에 쓰이는 sandbox.Dockerfile 파일 같은 걸 만들어주면 된다고 적혀있긴 한데 문서는 '그냥 이렇게 하면 됩니다'까지만 있고 안 되면 어떻게 해결하라거나 디버그 로그를 보려면 어떻게 하라는 게 없다.

 

더구나 샌드박스 안에서 Redis로 접속하는 구조가 되다 보니 127.0.0.1로 해결이 안 되는데 이걸 Redis MCP에 변수값으로 줄려면 ~/.gemini/settings.json 파일 안에 mcpServers 블럭을 넣어줘야 하는데 이것도 정확히 어떤 형식을 따라야 하는지 딱 복붙 예시를 주는 데가 없다. '여기까지 왔으면 이런 건 다 알고 있을 테니까 굳이 얘기 안 한다?' 뭐 그런 건가.

 

제미니 유료 결제를 하고 나서 이것저것 하기 위한 전단계가 꽤 지친다.

 

---

오픈소스 동네의 암묵적인 룰에 따라, 결국 원본 gemini-cli 소스코드를 찾아봤는데, 아무래도 샌드박스 이미지를 직접 빌드하는 기능은 npm에는 빠져있고 소스코드 안에서만 동작하는 것 같다.

https://github.com/google-gemini/gemini-cli/issues/3404

EDIT: Looking into it, this appears to be intended behavior. If you want to use gemini to build the sandbox then you need to install from source.

빌드에 쓰이는 스크립트 파일이 있는데 이게 npm으로 설치한 파일 중에는 없어서 찾아보니 여러 중복 이슈들 끝에 저렇게 코멘트가 남겨진 이슈가 있다.

이래놓고 공식 문서 샌드박스 설명에는 이딴 식으로만 적어놨단 말이지 구글놈들

Missing commands

  • Add to custom Dockerfile.
  • Install via sandbox.bashrc.

그야말로 '내 자리에선 되는데요'의 전형 아닌가 

It Works On My Machine: It Works On My Machine Funny Programmer Bug Shrug  Notebook - Funny 

 

하다 하다 volta가 아직 덜 흔해서 문제일까 nvm으로 바꿔보기까지 했는데. 내 시간 물어내라 구글놈들아

 

---

깃허브 이슈의 코멘트에 적힌 '아예 공식 이미지 경로처럼 태그를 적어서 빌드해주면 된다'는 내용이 먹히긴 한다. (애초에 sandbox.Dockerfile FROM에 저걸 적어줘야 하는데 나중의 빌드에서는 순환참조가 되는 게 아닌가 싶지만)

~/.gemini$ docker build --no-cache --progress=plain -t us-docker.pkg.dev/gemini-code-dev/gemini-cli/sandbox:0.22.5 -f sandbox.Dockerfile . 

 

이렇게 빌드하고 나서 gemini를 실행하면 redis mcp 에러 출력이 uvx 명령 없다는 것에서 접속 실패로 바뀌었다. spawn uvx ENOENT 대신 MCP error -32000: Connection closed

 

이제 settings.json 파일에 mcpServers 블럭을 붙여주면 끝이네 싶었지만, 그렇지 않았다.

sandbox.Dockerfile에 따로 redis-tools 패키지를 설치하고 gemini 안에서 !를 눌러서  redis-cli -h 172.17.0.1 info 도 해보고 redis-cli -h host.docker.internal info 도 되는 걸 확인했지만 여전히 mcp 초기화에서는 여전히 Connection closed가 뜬다. 그럼 docker나 redis 문제는 아니라는 건데 뭘 어떻게 해줘야 되는 거지?


https://redis.io/docs/latest/integrate/redis-mcp/client-conf/#claude-desktop

mcpServers 블럭 설정은 Redis MCP 공식 문서의 예시에서 type 키만 빼고 그대로 적용하니, gemini -s 실행 후에 에러가 안 나오고, /mcp schema 명령으로도 출력이 잘 나온다.

 

https://github.com/qdrant/mcp-server-qdrant

qdrant는 docker 실행하고 mcpServers에 추가해주는 것만으로 끝났다.

아니, 더 찾아보면 사실 제미니 자체는 이런 컨텍스트 관리를 외부로 확장하는 기능이 없으니 그렇게 쓰고 싶으면 ~/.gemini/GEMINI.md 같은 최상위 시방서에 잘 적어줘야 된다고 한다. 이 모험에서는 그냥 MCP 구성을 해봤다는 걸로 만족하기로 했다.

 

--- 

아래는 settings.json 파일 중 mcpServer 블럭을 그대로 복붙한 것. (from 인자를 안 주니까 디렉토리가 바뀌면 mcp가 해당 서버에 연결 못 하는 상황이 생김)

 "mcpServers": {
   "redis": {
     "command": "uvx",
     "args": [
       "--from",
       "redis-mcp-server@latest",
       "redis-mcp-server",
       "--url",
       "redis://172.17.0.1:6379/0"
     ]
   },
   "qdrant": {
     "command": "uvx",
     "args": [
       "--from",
       "
mcp-server-qdrant@latest",
       "mcp-server-qdrant"
     ],
     "env": {
       "QDRANT_URL": "http://127.17.0.1:6333",
       "COLLECTION_NAME": "gemini-cli",
       "EMBEDDING_MODEL": "sentence-transformers/all-MiniLM-L6-v2"
     }
   }
 }
 

---

기록을 위해 적어두자면 redis, qdrant는 docker로 실행해서 포트만 열어줌.


docker run --name redis-mcp -d -p 6379:6379 --restart=on-failure redis 

docker run --name qdrant-mcp -d -p 6333:6333 --restart=on-failure qdrant/qdrant

 

redis-cli를 열어두고 gemini에서 적당한 명령을 내려보면 redis에 접속 기록이 남는 것을 확인할 수 있다. 

172.17.0.1:6379> monitor
OK
1767225632.091199 [0 172.17.0.1:60296] "CLIENT" "SETINFO" "LIB-NAME" "redis-py(mcp-server_v0.4.1)"
1767225632.091377 [0 172.17.0.1:60296] "CLIENT" "SETINFO" "LIB-VER" "7.1.0"
1767225632.091540 [0 172.17.0.1:60296] "SCAN" "0" "MATCH" "session:*" "COUNT" "100"
1767225639.157522 [0 172.17.0.1:60296] "DBSIZE"

 

---

$ cat sandbox.Dockerfile  
#FROM gemini-cli-sandbox
FROM us-docker.pkg.dev/gemini-code-dev/gemini-cli/sandbox:0.22.5 AS gemini-cli-sandbox

USER root

# uv를 모든 사용자가 접근 가능한 전역 경로에 설치i
RUN curl -LsSf https://astral.sh/uv/install.sh | UV_INSTALL_DIR=/usr/local/bin sh
RUN uv --version
RUN uvx --version

RUN mkdir -p /tmp/uv_cache && chmod 777 /tmp/uv_cache
ENV UV_CACHE_DIR=/tmp/uv_cache

# Docker out of Docker method
#RUN apt-get update && apt-get install -y docker.io docker-compose # too old version
# TODO: copy from docker:dind image?
RUN apt-get update \
   && apt-get install -y ca-certificates curl \
   && install -m 0755 -d /etc/apt/keyrings \
   && curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc \
   && chmod a+r /etc/apt/keyrings/docker.asc \
   && tee /etc/apt/sources.list.d/docker.sources <<EOF
Types: deb
URIs: https://download.docker.com/linux/debian
Suites: $(. /etc/os-release && echo "$VERSION_CODENAME")
Components: stable
Signed-By: /etc/apt/keyrings/docker.asc
EOF
RUN apt-get update
RUN apt search docker \
   && apt-get install -y docker-ce \
   && /bin/bash -c 'echo -e "{\n \"iptables\": false\n}" > /etc/docker/daemon.json'

# for qdrant mcp
RUN mkdir -p /tmp/fastembed_cache && chmod 777 /tmp/fastembed_cache
RUN apt-get install -y python3-pip
RUN pip3 install --break-system-packages fastembed
RUN HF_HOME=/tmp/hf_home FASTEMBED_CACHE_PATH=/tmp/fastembed_cache python3 -c "from fastembed import TextEmbedding; import os; TextEmbedding('sentence-transformers/all-MiniLM-L6-v2')"

RUN npm install -g pyright #typescript typescript-language-server

USER gemini



FROM을 정확히 어떻게 적어줘야 하는지는 여전히 잘 모르겠다. 제미니 공식 문서에도 딱 깔끔하게 안내해주지는 않음. 공식 문서에서 안내하는 FROM gemini-cli-sandbox는 실제로는 빌드가 되지 않는 걸 봐선 저것도 소스 저장소 기반에서만 동작하는 걸 그대로 문서에 적어둔 게 아닐까 의심.

docker 설치는 docker out of docker라는 방식으로 호스트의 docker에 접속하기 위함. 프로젝트 자체가 docker-compose를 사용하면서 sandbox가 호스트의 docker에 접속하지 못하면 디버깅을 위해 제미니가 뭔가 실행해서 결과를 보려고 할 때 직접 할 수 없어서 사용자더러 실행하고 복붙해달라고 요청하게 된다. 무중단 실행을 위해서는 꼭 필요한 접근이다. 물론 보안성이 좀 떨어지긴 하지만 제미니 외에 다른 컨테이너 자체가 없다면 문제도 없을 것. 

qdrant 쪽은 처음에는 됐던 걸로 기억하는데 왜인지 어느 순간부터는 qdrant에 접속 못한다고 -32000 에러를 냈다. mcpServers의 명령행을 gemini 안에서 쉘로 직접 실행해보니 모델이 없다고 떠서 아예 sandbox.Dockerfile 안에서 미리 파일을 받도록 했다. 이게 확실한 해결책인지는 아니면 내부 쉘에서 수동으로 뭔가 한 게 효과를 낸 건지 잘은 모르겠지만 어쨌든 진행 과정중에는 에러가 없어진 걸 확인했다. 

lsp MCP는 serena라는 걸 써보려는 참. 작업하다가 변수 이름이나 인자 이름 같은 걸 꽤 자주 틀리고 쉘 스크립트 문법 같은 것도 틀려서 고치고 다시 실행하는 걸 자주 봐서 LSP 역할이 필요하긴 하구나 싶다. 찾아보니 깃허브 이슈에 남아있어서 아직 정식 지원은 아닌가 싶기도 하지만 일단 /mcp list 목록에는 드니까. -> lsp를 활성화한 뒤에 꽤 규모 있는 여기저기 다 바뀌는 작업을 시켜봤는데 막 코드 짜다가 아 이 필드는 사실 다른 이름인데? 아 이 데이타는 없는데 로직상 필요하네? 이러면서 스크롤을 이어가는 모습이 보인다. lsp 효과인지 아닌지는 잘 모르겠다. 어디를 봐야 lsp를 써먹고 있는지 알 수 있나? -> "replace_content (lsp MCP Server)" 라는 블럭이 지나가는 걸 봐서는 진짜 써먹고 있긴 하다. 붙여주면 도움이 되긴 하는구나. 근데 MCP가 토큰 많이 쓴다는 얘기를 봤던 거 같아서 약간 신경 쓰임.

 

gemini라는 계정은 실제로는 없음. USER를 따로 지정하지 않은 상태로 RUN으로 뭔가 권한 없을만한 명령을 실행하면 gemini라는 계정이 없다고 에러 나는 걸 확인했던 것으로 기억.

샌드박스 컨테이너에 docker inspect 해보면 실시간으로 그룹과 계정을 추가하는 것처럼 보임. export SANDBOX_SET_UID_GID=false 플래그를 켜면 그런 계정이 passwd 파일에 없다고 샌드박스 자체가 안 뜨는 장면을 볼 수 있음. 기본적으로는 true로 동작하는 결과로 추가하는 것으로 보임.

https://github.com/google-gemini/gemini-cli/blob/8f0324d868904bece4d370d06d5a3ed275ae9ade/packages/cli/src/utils/sandboxUtils.ts#L39

https://github.com/google-gemini/gemini-cli/blob/8f0324d868904bece4d370d06d5a3ed275ae9ade/packages/cli/src/utils/sandbox.ts#L574 

소스코드를 보면 true도 false도 아니면서 데비안이나 우분투면 (아마 최상위 FROM 자체가 여기에 기반하고 있어서) 호스트 UID, GID를 넘기는 게 기본이고, 그 외에는 false로 처리하는 방식.

계정과 그룹을 실시간으로 추가하기 때문에 docker run --group-add 식으로 추가 그룹을 설정해도 먹히지 않는 걸로 보이는데 docker out of docker 방식을 위해 호스트의 groups 중에 하나를 샌드박스 안쪽까지 전달하려면?

좋지 않은 방법이지만 일단 호스트에서 sudo chmod o+rw /var/run/docker.sock 처리해서 샌드박스에서 docker ps가 소켓을 읽을 수 있는 건 확인했다.

2025년 11월 1일 토요일

우분투를 amd64v3로 바꿨더니 firefox가 없어졌다

https://discourse.ubuntu.com/t/introducing-architecture-variants-amd64v3-now-available-in-ubuntu-25-10/71312

 

우분투가 amd64 아키텍처 중에서 최근 버전 대응 패키지를 제공하기 시작했다.

wayland 지원도 그렇고 rust 재구현도 그렇고 변화를 선도하는 시도가 반갑다.

 

하지만 문제가 생겼다. 우분투가 언젠가부터 firefox를 snap으로 제공하기 시작했는데 이게 싫어서 PPA로 따로 설정해뒀던 게 apt policy firefox 명령에 나오지 않았다. 찾아보니 amd64v3 지원이 안 되는 저장소는 amd64를 쓴다고 하기도 하고 논리적으로도 이제 막 지원하기 시작했는데 지원이 안 되면 안 쓰게 하는 게 당연하다.

apt update 명령에서 mozillateam 저장소가 무시되는 것도 아니었다. amd64v3 지원 안 된다는 경고도 뜨지 않았다.

그러면 amd64v3 대응 패키지 목록은 어떻게 되는지 확인하려고 Package.gz 파일을 받아서 zcat으로 열어봤더니 thunderbird 관련만 있고 firefox가 없다. 왜죠...?

 

apt 설정에서 특정 저장소는 amd64v3보다 amd64를 선호하게 하는 옵션 같은 게 있을까 찾아봤지만 잘 나오지 않았다.

ubuntu questing (=25.10) 대신 plucky (=25.04) 대응 저장소는 amd64v3 대응이 아직 안 되어 있어서 일단 저장소 설정을 plucky로 바꿔서 firefox가 다시 나타나는 걸 확인했다.

plucky에 amd64v3 지원이 들어가는 시점이면 아마 questing 패키지가 다 빌드된 시점일 테니 다시 옮겨가면 문제 없을 거라 기대중이다. 

 

---

특정 저장소에 아키텍처 설정하는 건 우선순위 설정은 아니고 그냥 뭘 쓰라고 지정하는 식인가보다.

Architectures: amd64

이런 식으로 apt 저장소 설정에 추가해주면 지정한 아키텍처로만 가져오는 모양.

ubuntu resolute에서 6.18 커널을 설치 못 한다고 의존성 깨진다고 자꾸 뭐래서 우분투 공식 패키지 검색도 해보고 제미니랑 이것저것 상의해보니 amd64에만 올라간 패키지가 있어서 amd64v3 기반의 설치에서는 아예 패키지가 없다고 안 잡혔던 모양. 그래서 resolute 저장소에만 amd64 아키텍처 쓰라고 지정하니까 6.18도 잘 깔렸다.

그동안 amd64v3 아키텍쳐 지원 안 한다고 저장소마다 경고 뜨던 것도 직접 아키텍쳐 지정해서 다 깨끗하게 정리했다. 

2025년 9월 28일 일요일

TIL: GraphRAG

애초에 연관계가 있는 데이터는 graph 정보까지 같이 저장해서 RAG에 쓰나봄. 그동안은 주로 영상 쪽으로만 봤더니 이런 게 필요하다는 상황 자체를 인지 못했음.


"지금까지 Vector RAG를 살펴보았다면, 이제 또 다른 RAG, Graph RAG에 대해 알아보고자 합니다. 단순히 '의미가 비슷한' 정보를 찾는 것을 넘어, 정보들 사이의 '명시적인 관계'와 '구조적인 맥락'을 활용하여 LLM의 답변 능력을 한 단계 끌어올리는 접근 방식입니다." https://mz-moonzoo.tistory.com/94


개인 위키를 다시 만들 궁리를 하면서 방대하고 다양한 주제로 파편화된 문서들 속에서 관련문서라는 정보를 어떻게 잘 뽑아내야 하는지 아리송했는데 아마 이게 들어맞지 않을까 싶음.

 

https://hub.docker.com/r/apache/age (왜 설명에 이미지가 깨졌는지 모르겠지만 어쨌든 최근까지 업데이트가 있어서 프로젝트는 살아있는 걸로 판단)

저장 쪽을 찾아봤는데 pg_vector와는 또 별개로 구현체가 있음.

https://bitnine.tistory.com/542 보면 둘 다 쓰는 것도 되긴 하나본데 둘 다 설정된 docker 이미지는 아직 못 찾음. https://github.com/apache/age/issues/1121 같이 수요는 있음.

2025년 9월 19일 금요일

쉘 스크립트에서 xargs를 썼다가 파일명에 공백 하나가 아니라 공백 두 개가 있을 때를 완전히 놓친 이야기

xargs를 자주 쓰는 편인데 한동안 잘 썼던 스크립트를 어쩌다 검산할 일이 생겼다.

정제해서 뽑아뒀던 파일 목록이 실제로 잘 있는지 [ -e "$FN" ]으로 검사해봤는데 분명 눈으로는 있는 파일이 스크립트에는 없다고 잡힌다.

맥과 리눅스 환경 차이 때문에 생기는 인코딩 문제일까 해서 convmv도 돌려봤지만 이상 없고.

 

그래서 직접 탭 눌러가며 파일 경로를 자동완성 해보니 그제야 문제가 보였다. 스페이스가 두 개 들어간 파일명이었다.

정제 단계에서 xargs를 거치는 부분이 있는데 이게 공백 여럿을 공백 하나로 바꿔버린 거였다. xargs에 -0 옵션을 주라거나 find 자체에서부터 -print0 옵션을 쓰라거나 하는 얘기들이 있는데 이건 좀 번거로워 보였다.

xargs echo xyz 처럼 STDIN을 xargs 통해서 뭉치던 걸 xargs echo -n | cat - <(echo " xyz")
 처럼 뭉치는 걸로 바꿨더니 증상이 해결되었다. echo 자체가 STDIN을 받아주면 좋을 텐데 그런 건 없는 모양.

2025년 9월 18일 목요일

명세만 작성하면 코드가 뚝딱?

깃허브에서 Spec Kit을 발표했다. 예전에는 Copilot Workspace라고 해서 웹페이지에서 요구사항을 입력하고 실행계획을 짜고 코드를 적당히 만들어서 PR로 제출해주는 기능이 맛보기로 잠깐 나왔다 종료됐는데, 이번엔 그 기조를 유지하면서 '봇에게 일정한 동작 규칙을 파일로 부여하는 게 좋다'거나 '큰 요구사항 하나를 주기보다는 작은 단위로 쪼개서 주라'거나, '봇에게 매번 채팅으로 입력하는 것보다는 작업 지시서를 주고 그걸 따라 진행하게 만드는 게 좋다'는 그간의 활용법을 일정한 틀로 뽑아냈다.

 

Copilot Workspace 당시에 잘 썼던 기억이 있어서 Spec Kit도 좋은 이미지고, vscode에서 개인 프로젝트의 명세만 작성해보았다. LLM의 context 제한에 맞춰 작업 범위를 줄이면 좋고 따라서 에이전트도 역할별로 나누라거나 작업대상 파일을 나누라거나 하는 얘기를 볼 때면 이론적으로 그렇구나 하고 넘어갔었는데 vscode에서 /specify 명령을 여러 번 입력하면서 명세의 틀이 잡혀가는 걸 보니 그게 이런 얘기였구나 싶다.

AI 활용법 경험담 중에 '깃허브 이슈에 할일을 쭉 적어두고 여러 AI CLI의 무료분을 활용해서 자동적으로 처리하게 만든다'는 얘기가 꽤 솔깃했는데 Spec Kit도 비슷하게 '명세만 잘 작성해두고 시작!이라고 외치면 이런저런 AI의 무료분을 활용해서 매일 조금씩 개발'하는 방식으로 쓰일 수 있을까 해서 기대가 된다. (어떤 의미론 '선언적 프로그래밍'이라고 해야 할까?)

 

vscode의 github copilot 창에서 써봤다.

1. 파일 바꾼 걸 매번 승인하라고 뜨는데 채팅 안에서의 자동 동작은 파일이 이미 승인되었다고 전제하고 수행되어서 '이상하네 파일이 없어요'하고 멈추는 경우가 종종 생겼음. 찾아보니  chat.editing.autoAcceptDelay 설정이 0으로 되어서 비활성화였고 1로 바꾸니까 생성 직후에 바로 승인되어서 이어지는 명령들이 부드럽게 진행됨.

2. 작업을 하다 보면 쉘로 npm, npx, git 같은 명령을 수행하게 되는데 이것도 자꾸 승인하라고 떠서 결국 chat.tools.terminal.autoApprove에서 rm 같은 몇 가지만 제외하고 /.*/로 전체 허용하고 말았다. git add && git commit 같이 두 명령을 붙여서 실행하는 경우에 vscode 설정의 설명으로는 git add와 git commit이 허용되어 있으면 당연히 쌍으로 붙은 것도 허용될 것 같았는데 실제로는 매번 물어왔다. 이건 전체허용 뒤에도 여전히 물어서 뭐가 문제인지 오리무중.

3. 자동 생성된 텍스트가 vscode에서 자동으로 formatter 처리되어서 약간씩 문법만 달라지는 경우가 생기는데 이건 설정은 못 봤고(FIXME), 채팅에 '생성한 파일은 lint도 자동으로 한 다음에 커밋'하라고 지시하니가 그렇겠다고 하고 실제로도 그렇게 동작했음 (next.js 초기화 과정에서 선택한 biome를 매번 파일마다 실행하는 방식)

4. @/xyz/AnyFile 같은 경로를 인식하지 못해서 일단 수동으로 상대경로로 다 바꿔줬는데 이건 나중에 일괄로 바꾸면 되니까. 자동 생성 때 뭐가 문제였는지도 나중에. 

 

쭉 진행해보니 자잘한 빨간줄들은 대충 해결하면서 tasks.md의 끝까지 진행했다고 나온다. 막상 하고 보니 UI 쪽은 아예 최초 명세에서부터 빠져 있어서 E2E 테스트와 함께 덧붙여가야 하는데, 이 과정이 모호하게 느껴진다. 아마 채팅창에 직접 이것저것 넣는 부분을 /specify와 /plan으로 녹여내서 명세서를 보완해야 할 것 같긴 한데. 그냥은 반영 안 하고 꼭 spec.md 파일을 직접 열어둔 상태에서 /specify 문구를 넣어야 반영하는 식인가?

 

자동 변경이 아니거나 하다가 잘 안 된 찌꺼기들은 자동 커밋이 안 되고 남는 것 같은데 이게 좀 번거롭다. 커밋하라고 명령하면 커밋 메시지까지 자동으로 만들어서 넣으니까 별 문제는 아니지만.

 

근데 깃허브 코파일럿이 이렇게 쿼터를 많이 주나? 엄청 많이 쓴 건 아닌 것 같지만 그래도 중간에 코파일럿 무료 허용량 다 썼다고 멈출 줄 알았는데, 3시간 좀 넘게 이것저것 해서 next.js로 URL 2개 접속되는 거 볼 때까지 중단없이 잘 진행이 된다.

https://github.com/keizie/wiki-engine

리눅스에서 스팀, 에픽 게임이 된다고?

Fedora Asahi Remix를 통해 M1에 리눅스를 깔아서 쓰면서, Asahi Linux 팀의 소식을 접할 때 '그래픽 드라이버가 많이 발전해서 이제 게임 성능이 아주 좋아졌어요!' 같은 내용을 비교적 자주 보았다.

물론 스팀OS 같이 비-윈도우 환경에서 게임 기동이 된다는 건 당연히 알고 있었지만 Wine이니 CrossOver니 하는 예전의 경험들 때문에 그 과정을 직접 하는 건 아주 번거로울 거라고 생각해 시도도 안 했고 게임이라는 건 응당 윈도우로 부팅해서 한다고만 생각했었다.

하지만 재부팅을 한 번 할 때마다 시간도 걸리고 번거로운 점들이 있어서, 기왕에 아사히 소식을 통해 '스팀을 깔면 된다'더라 하는 얘기들도 있어서 시도를 해봤다. 그리고 마침내 나도 재부팅 안 하고 어지간한 게임을 할 수 있다는 걸 깨달았다.

 

스팀은 애초에 우분투에는 정식으로 APT 저장소가 있고, RPM 쪽도 저장소가 있는 것 같은데 아사히에는 아사히 팀이 직접 관리하는 저장소가 딸려온다. 에픽은 https://github.com/Heroic-Games-Launcher/HeroicGamesLauncher 라고 이름이 살짝 다른 구현체가 존재한다. (GUI는 heroic, CLI는 legendary라는 이름인 게 매력 포인트)

M1의 aarch64 아키텍처는 스팀이 지원하고 에픽은 amd64만 된다. 스팀에서 기나긴 다운로드 시간을 감수하고 몇 가지를 깔아봤지만 규모가 크다 싶은 게임은 그래픽 문제보다는 메모리 문제로 뜨다가 갑자기 끝나버린다. 스팀 OOM이 명시적으로 뜨는 경우도 있고 아닐 때도 있고. 아사히 블로그 글에도 보면 에뮬레이션 비용이 더 들다보니 16기가 램이 필요하다고 되어 있다. 나의 작은 M1은 기본형 8기가 램.

 

그래서 나는 서버로 쓰던 amd64에 스팀과 히로익을 모두 깔아보았다. 램도 그래픽 자원도 풍부하다. 서버 프로세스가 바쁠 때 영향을 받아서 좀 밀린다는 것과 게임용-윈도우가 SSD인 것에 비해 디스크 읽기가 현격히 느려서 로딩이 하세월이라는 것 정도가 아쉽다. 예전 어딘가에서 전설처럼 들은 '서버실 성능 좋은 머신에 가상머신으로 스타 여러 개 깔아서 하고 있더라'는 일화가 생각난다.

또 나는 게임에 트레이너 도움을 받는 걸 즐기는데 https://github.com/DeckCheatz/wemod-launcher 라고 아예 스팀 아래에 트레이너를 끼워넣어서 같이 띄워주는 구현체도 있길래 이 역시 적용했다.

여기까지 프로젝트를 끌고 온 사람들 모두 대단하다. 복어회를 먹기까지 끝없는 시행착오를 반복했을 그 끈기가 여기에도 적용되었을 것이다. 

2025년 9월 10일 수요일

코드가 법이다, 그 후

"옥주현, 수년간 소속사 불법 운영···형사처벌 대상"이라는 제목의 기사를 접했다. "대중문화예술산업발전법상 법인과 1인 초과 개인사업자로 활동하고 있는 연예인은 대중문화예술기획업으로 등록해 활동해야 한다. 이는 필수적 법적 요건으로 위반할 시 형사 처벌을 포함한 법적 제재를 받는다."고 하여, 법령상 특정 업종으로 등록해야 하는데 누락했으니 '불법 운영'이라는 내용이었다. 기사에는 실수로 누락했다는 해명이 있었다. 나는 이 해명이 맞고 기사가 악의적으로 나왔다고 짐작한다. (+ 그 며칠 뒤엔 성시경도 같은 이유로 기사가 났다. 아마 누군가 날 잡아서 이름 있는 연예인들 소속사마다 법인의 업종을 다 추려본 모양)

 

대한민국 법령 체계가 어떤 국가적 개념들이 모인 단일한 뿌리에서 자라나는 게 아니라 실무 주체인 주무기관이 있고 그 주무기관 산하에 소관법령이라는 형태로 나열된다는 걸 깨닫고 놀랐었다. (예: 산림청 소관법령) 그리고 각 주무기관은 자기 사업을 각자 영위하고 거기에 필요한 법률을 각자 정부입법으로 만들어낸다. (중간과정 매우 생략 주의) 이 과정에서 개념적으로는 인접한데 소관이 갈리는 경우가 생기면서 일반인이 보기엔 이상한 경우도 생긴다. (예: 화재는 소방청 소관이지만 산불은 산림청 주관이고 소방청은 협조)

 

이 때문에 일반 시민들은 시시 때때로 바뀌는 법령을 잘 따라가지 못하고 때로는 '담당 공무원이 나보다 몰라서 관련 규정을 차근차근 알려줘야 했다' (예: 캐스퍼 전기차는 경차 아니고 소형차여서 취득세 부과) 류의 경험담이 종종 보인다. 일상생활에 밀접한 세법, 건축법, 소방법 같은 건 각 분야 전문가가 업무적인 고도의 전문성과 함께 법령을 꾸준히 따라가는  전문성도 구비해야 한다.

이 상황에서 공무원을 욕하고 다그친다고 상황이 나아질 것 같지도 않다. 이미 일선 공무원은 주먹구구식으로 업무를 분장하고 혹은 그마저도 잘 지켜지지 않고 한쪽으로 쏠려서 부담을 느껴 면직하거나 목숨을 끊거나 하고, 순환 보직으로 계속 바뀌는 와중에 인수인계라는 게 존재하지 않아서 계속 새 사람이 전혀 모르는 일을 떠맡는다고 한다.

 

나는 옥주현의 소속사도 행정 절차 중 하나를 누락했을 뿐이고 억울한 입장이라고 이해했으므로, 이렇게 억울한 상황이 어떻게 생겼는지 그리고 어떻게 하면 앞으로는 생기지 않을지 궁금했다. 그래서 ChatGPT를 열어서 물어보았다. 다음은 내가 입력한 질의들이고 수정이나 재시도는 없이 한 번에 쭉 진행되었다.

  1.  https://m.entertain.naver.com/home/article/144/0001066309 이 기사를 읽을 수 있나?
  2.  좋아. 나는 실수로 누락했다는 주장이 그럴듯하다고 생각해. 그래서 https://law.go.kr 에서 정보를 찾아봤지. 아래 URL은 검색 결과 페이지야. https://law.go.kr/lsBylSc.do?menuId=9&subMenuId=55&tabMenuId=261&query=%EB%8C%80%EC%A4%91%EB%AC%B8%ED%99%94%EC%98%88%EC%88%A0%EA%B8%B0%ED%9A%8D%EC%97%85#liBgcolor2 https://law.go.kr/LSW/aai/searchList.do?query=%EB%8C%80%EC%A4%91%EB%AC%B8%ED%99%94%EC%98%88%EC%88%A0%EA%B8%B0%ED%9A%8D%EC%97%85&pageNum=1&pagePer=10&searchType=1&modelYn=N&indexNames=&version=&ignoreWord=&keywordSearchYn=N&searchResultLsKndCd=0&firstSearchKeyword=&preSearchKeyword=&searchKndCd=0&mainType=main&detailSearchYn=N&lsChapCd=&lsKndCd=&upCptOfiCd=&cptOfiCd=&startAncYd=&endAncYd=&startEfYd=&endEfYd=&startPrmlYd=&endPrmlYd=&startAdmRulEfYd=&endAdmRulEfYd=&sortCode=0&bylClsCd=&chk= 검색 결과를 보면 일단 법률 수준에서 바로 대중문화예술기획업이라는 키워드가 나오지는 않는 것 같아. 법률 하위의 시행령이나 규칙 같은 걸로 내려가야 실질적인 용어로 검색이 되고. 여기서 내가 너한테 요청하고 싶은 건, 대중문화예술기획업 같은 업종을 등록하는 행정절차가 필요하다는 걸 당사자 시민이 알려면 어떤 문서들을 어떻게 파악해야 하는 건지 니가 심층적으로 검토해줬으면 해.
  3.  너는 애초에 저런 법이 있고, 저런 업종에 대한 등록 의무가 있으리란 걸 인지한 사람을 전제로 하고 그 이후의 실무 절차를 설명하고 있다고 이해했어. 내가 궁금한 건 애초에 저런 법이 있어서 따라야 한다는 사실 자체를 시민 당사자가 어떻게 예상할 수 있냐는 거야. 바꿔 말하면 내가 하고자 하는 행위가 행정적으로 그리고 법적으로 어떤 의미가 있고 어떤 법률에 영향을 받는지 어떻게 알 수 있지?
  4.  좋아. 내가 질문한 방향에 맞게 답이 된 것 같아. 하지만 결국 1) 일반인은 알기 어려운 게 사실이고 2) 정부에서 잘 알려줘야 하고 3) 누락은 여전히 생길 수 밖에 없다 ...라는 거잖아? 니가 정리는 잘 했지만 결국 실상은 매우 실망스럽네. 그럼 가급적 최신 연구나 세계 각국의 실험적 행정을 검색해서 이 점을 개선하려는 시도가 있었는지 봐줄 수 있나?
  5.  다음 단계로 제시된 3가지 중에서 2와 3을 볼 수 있으면 좋겠네.
  6. B를 해줘. A는 B가 끝난 다음에 진행할지 판단할 테니까 다시 물어봐줘.
  7.  B의 결과는 기대보다 미흡하네. A를 보여줘.
  8.  1,2 모두 지금 관심사는 아니야. 오히려 궁금한 건, 위의 yaml을 원래의 법령에서 추출할 때 각 조항을 어떤 문법에 따라 분석하고 yaml로 추출할 조항과 무시할 조항을 선택했는지야.
  9. 좋아. 실제 원문에서 추출한 yaml을 보여줘. 
  10. 명확한 패턴이 있는 조문에서 if-then-else를 뽑아냈다는 건 이해했고 결과도 좋아 보여. 그럼 반대로 원문에는 있지만 yaml 추출에 사용되지 않은 조항이 유의미한 정보를 담고 있는지 경우를 파악할 수 있을까? 그러니까 다시 말하면 yaml에 충분한 규정이 담기지 못하는 경우를 막을 수 있을까? 
  11. 좋아. yaml에 없는 보조 정보가 어떻게 정리되는지 예시를 보여줘.
  12. (이 뒤에 변환 코드까지 달라고 해봤지만 매우 단순한 문자열 감지뿐이어서 참고는 되지 않음) 

 

ChatGPT에 적힌 내용을 모두 신뢰하는 건 아니고 큰 틀에서 어떤 키워드가 있는지만 본다면 Rule as Code (RaC)가 가장 그럴듯하다. 이걸로 검색을 해보면 OECD 문서가 나오기도 해서 아무말이나 지어낸 게 아니라 시중에 진짜로 쓰이는 용어인 것 같다.

개념 자체는 이미 알고 있다. 2006년 출판, 2009년 한국에 번역된 로렌스 레식의 코드 2.0을 당시에 읽었고 그 전에 나왔던 저작들도 접했었다. 또한 오픈소스 동네에서 지내며 실생활에 프로그램이 어떤 영향을 끼치는지도 체감했기 때문에 매일매일 변동증감하는 코드가 그 자체로 곧 규정으로 기능한다는 개념도 쉽게 수용했다. 아마 영어권 화자라면 code라는 말 자체를 프로그램과 규정으로 구분해서 받아들일 필요 없이 그냥 중의적인 표현으로 보았겠지.

ChatGPT와의 대화가 끝으로 가면서는 실제 법률을 어떻게 코드화는지 사례를 보여주기에, 개별 사례보다는 코드화 그 자체의 원리나 패턴을 물었고, 실제 문언에서 if-then-else를 어떻게 뽑아내었는지 설명이 제시되었다. 애초에 법령 자체가 코드라면 이런 변환 과정 자체가 없어도 되고 문맥에 따라 해석이 달라질 수 있는 경우도 더 적을 것이다.

 

코드가 법이란 건 알고 있다. 다음은 법률이 코드여야 한다의 순서인가? 

리눅스에서 지문 인증이 된다고?

예전처럼 리눅스 커널 메일링리스트를 받아보는 건 아니지만 M1에 아사히 리눅스를 깔아서 쓰고 있기도 하고 2025년 여름 즈음으로 어쩐지 커널 버전업이 활발하기도 해서 종종 이런저런 채널로 커널 소식을 접할 때가 있고 마침 유튜브에 이번 주의 그런 영상이 떠서 보았다.

 

https://github.com/xapp-project/fingwit

민트 리눅스라는 배포판에서 위 프로젝트를 기본 내장해서 지문 인식을 도입한다는 모양이다. 민트를 쓰지는 않다보니 바로 확인해볼 수는 없겠다.

기왕 M1에 리눅스를 깔아서 쓰고 있기도 하고 이미 터치ID 딸린 애플 키보드를 하나 가지고 있기도 해서 (맥미니도 그렇고, 맥북도 조개 모양으로 닫아놓고 쓸 때는 외장 키보드에 지문 인식이 붙은 게 좋긴 좋다), 당연히 저 프로젝트 주변으로 터치ID 키워드가 존재할 거라고 생각했다.

 

linux "fprintd" apple touch id -site:apple.com

하지만 놀랍게도 fingwit으로는 전혀 나오는 게 없고, 더 하위의 fprintd로 찾아보니 좀 나오는 게 있다.

https://www.reddit.com/r/archlinux/comments/1mc7wup/touch_id_with_hyprland/?tl=ko

https://itsfoss.com/fingerprint-login-ubuntu/

아마 fingwit 말고도 이미 기존 구현체들이 있어서 우분투 그놈에서는 바로 설정 가능한 모양이다. 적어도 스크린샷으로 보이는 건 그렇다. 민트는 개중 새롭고 가볍고 뭐 그런 구현체를 채택한 거겠지.

 

이렇게 되면 아사히 리눅스 동네에서 fprintd 어쩌구 하는 얘기가 많지 않은 게 오히려 이상하게 느껴진다. 리눅스를 쓰는 집단들 중에 지문 인식기 달린 장치를 보유했을 가능성이 가장 높을 것 같은데. 

2025년 9월 1일 월요일

큰 화면으로 전자책을 보기 위한 모험

주로 전자책에서 TTS를 켜서 귀로 듣는 편이다. 종이책을 직접 다 보기엔 눈도 피곤하고 금방 집중력도 떨어지곤 해서다. 전에는 교보문고 전자도서관 등에서 자치단체 공공도서관을 통해 읽곤 했는데 얼마 전에 밀리의 서재에서 AI TTS라는 게 나와서 목소리 품질이 꽤 좋아져 만족스럽다.

그렇다고 아예 화면을 안 볼 수도 없는 게, TTS가 읽어준 게 억양과 발음이 모호해서 무슨 말인지 직접 봐야 한다거나 앞에서 무슨 말을 했길래 이 대목이 이렇게 된 건지 다시 봐야 한다거나 가끔은 정말 눈으로 직접 보고 싶은 인상적인 대목도 있거나 등등 여러 경우가 있다. 거대한 확대경이 붙은 자동 발화 독서대 같은 거랄까.

 

하지만 전자책 시장의 DRM 정책은 이 과정을 거의 불가능하게 만든다. 안드로이드 기기에서 크롬캐스트를 통해 화면을 공유하면 전자책은 그냥 검게만 나온다. 전자책 화면은 DRM 기술로 제한되어서 다른 기기로 전송할 수 없다는 게 그 이유다.

그래서 방법을 찾다가 결국 안드로이드 기기 자체가 DP Alt로 외부 모니터 출력을 지원하기도 한다는 걸 알았다. 이 기능이 들어가면 기기 가격이 꽤 비싸져서 결국 2025년 하반기인 현재에 2023년 출시 기기를 중고로 사야했다. Y700 2세대.

 

드디어 큰 화면으로 볼 수 있게 되었다. 하지만 노트북에 외장 모니터를 연결하는 거랑은 차원이 달랐다. 안드로이드 기기에 USB-C 케이블로 모니터를 연결하면 그 전의 해상도 설정은 사라지고 무조건 그 모니터가 지원하는 최대 해상도로 설정되었다. 4K 모니터이기 때문에 FHD로 다시 바꿔야 글자를 눈으로 따라읽을 수 있는 정도가 된다. 모니터를 뺐다가 다시 꽂을 때도 그리고 해상도를 바꿀 때도 아마 이 기기는 디스플레이 공간을 완전히 새로 구성하는 것인지 거기에 떠 있는 앱은 몽땅 초기 상태로 되돌아간다. 손이 많이 간다. 2023년 구세대 기기라 그런 건가 하는 생각도 들긴 하는데 OS가 나름 최신이니 그런 문제는 아니겠지.

USB-C로 연결한 뒤에 오디오 출력도 약간 곤란한 문제가 있었다. 기본적으로는 안드로이드 기기 자체 스피커로 소리를 내거나 연결된 모니터 스피커로 소리 내는 선택지가 있는데 나는 이미 그 모니터는 크롬캐스트 기기에 연결하면서 저품질 내장 스피커는 음소거로 바꾼 상태라 모니터로는 소리가 나지 않는다. 얼마 전 가구 배치를 새로 정리하면서 스피커에 입력 포트를 다 써버려서 더 뭘 어떻게 하기도 어렵다. 결국 이미 스피커에 물린 리눅스 장치에서 블루투스 오디오 프로파일을 활성화해서 안드로이드가 블루투스로 소리를 보내면 블루투스 신호를 받은 리눅스가 거기 물린 스피커선으로 소리를 다시 보내는 식으로 일단 정리를 했다.


여기까지만 해도 이미 꽤 번거로운데 밀리의 서재 앱이 한결 더 상황을 곤란하게 만든다. 교보문서 전자도서관 앱이나 교보문서 e북 앱은 안 그런데, 밀리의 서재는 외부 모니터로 띄워두고 TTS를 재생한 상태에서 블루투스로 연결했던 마우스를 연결 해제하면 TTS 재생이 멈춘다. (외부 모니터 없을 때는 멈추지도 않는다) 모니터를 연결하고 나면 기기 본 화면을 터치패드 모드로 바꾸는 기능이 있긴 하지만 마우스랑은 편리함이 완전 다르기 때문에 가급적 마우스를 쓰고 싶었을 뿐인데. (로지텍 마우스라서 연결된 3가지 기기 중에서 잠깐 바꾸면 되는 수준이다) (+ 리모콘 크기의 터치패드 달린 키보드를 찾아봤지만 가격이 싼만큼 품질도 안 좋다고 함)

그래서 이걸 밀리의 서재에 1:1 문의로 넣었더니 '블루투스 기기에 대한 최적화가 진행되지 않았다'고 질문 내용에 대해서는 한 문장으로 답변을 마치고 그 뒤에 이어지는 문장들은 '외부 화면은 차단하고 있는데 왜 그게 된다는지 모르겠고 아마 그 자체가 에러' 운운으로 길게 이어진다. '너 혹시 나의 소중한 DRM을 깨뿌순 거니??' 하는 붉고 굵은 글자가 눈에 보이는 것만 같았다. 어쩌면 다음 밀리앱 업데이트에는 아예 DP Alt 안에서도 못 쓰게 되는 건가 괜히 무서울 따름이다.

'큰 화면으로 보려는 수요가 있다는 걸 무시하지 말아달라'고 다시 답을 달았다. 당신의 눈이 언제까지나 작은 화면의 작은 글씨를 감당해낼 수 있을 거라 장담하지 말라.

2025년 8월 26일 화요일

python with 문법이 js using 문법으로

시간이 좀 지나 MDN을 다시 보니 Node.js 24부터 지원한다고 되어 있다. 

 

---

이런저런 프로그래밍 언어들의 파편을 구경하면서 유용하다고 생각했던 것들이 몇 가지 있다.

파이썬의 with 문법도 그 중 하나다.

(검색을 더 해보니 C#에도 똑같은 용도로 using이 있나보다. https://onepredict.github.io/using-keyword/#%EC%84%A0%ED%96%89-%EA%B8%B0%EC%88%A0 )

 

https://www.geeksforgeeks.org/python/with-statement-in-python/

with open("example.txt", "r") as file:
    content = file.read()
    print(content)  # File closes automatically

이렇게 한 블럭 안에서만 쓰고 버릴 변수를 만들어준다. 

직선적인 흐름에서는 열고 닫는 게 자연스럽지만 early return 같은 경우에는 그 앞에 만들었던 이런저런 연결을 닫아준다던지 하는 작업 때문에 return 앞부분이 번잡해지는 경우가 생기는데 그럴 때 유용할 것이다. 

 

PHP는 이런 게 없는 걸로 알고,

js는 with 문법 자체는 있지만 as 변수명으로 이름을 명시하지 않고 그 블럭 안에서 this를 대체하는 방식이어서 여러 단점이 있고 MDN에도 비추라고 구분되어 있다.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/with

 

그래서 js에 블럭 안에서만 유효한 let이 생긴 뒤로는 따로 블럭만 하나 열어서 그 안에 let으로 변수를 만들어주는 식으로 가끔 썼던 기억이 있다.

https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Statements/let

  

그러다 using 문법이 들어온 걸 알게 되었다.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/using

https://github.com/tc39/proposal-explicit-resource-management

typescript에서는 이미 몇 년이 넘은 제안이다. MDN 문서 기준으로 Node.js는 지원을 안 하지만 다른 브라우저들은 지원하는 게 이유가 있었다.

 

https://deno.com/blog/updates-from-tc39#explicit-resource-management-using

https://deno.com/blog/v2.3#explicit-resource-management-and-the-using-keyword 

Deno에서는 2.3 버전부터 지원했다고 한다. 2025년 4월 말에 발표된 버전이니까 여기도 지원이 아주 오래된 건 아니다.

Node.js에도 곧 반영이 되면 좋겠다.

2025년 8월 22일 금요일

발 달린 가구 대통합

평면도에서 자리를 차지하는 가구를 4개에서 2개로 줄이려고 작업중이다.

 

우선 튼튼한 선반을 들여서 벽 한 면을 바닥부터 천장까지 다 쓸 수 있게 수납 공간을 만들고 컴퓨터 관련 장비들을 몰아넣고 모니터 위로 책도 밀어넣었다.

거실장 tv장 에어컨장 거실수납장 티비장식장 티비대 오픈장 거실책장 우드인테리어 - 한솔홈 

모양은 다르지만 이 구도랑 비슷하겠다.

 

이것만 해도 장점은 뚜렷하다. 책장이 따로 있을 때는 어쩐지 눈길을 많이 타지 않아서 먼지만 쌓여가는 쓸쓸한 공간이었는데 지금은 어쨌든 가장 오래 시야가 닿는 방향에 책도 같이 있다보니 책에 시야가 더 간다.

스피커도 기존에는 선을 최대한 길게 써서 공간 전체의 좌우에 두었는데 이번에 저 사진과 비슷하게 모니터 좌우로 배치를 바꿨고 스피커가 허용하는 입력단자를 총동원해서 3개 장치의 입력을 한 번에 틀어둘 수 있게 정리했다. AUX선에서 잡음이 실릴까 걱정했는데 아예 없진 않지만 평소에 인지하지 못할 정도여서 다행히도 문제없이 일단락 지었다. 

그리고 모니터 앞쪽으로는 원래 책상으로 쓰던 테이블에서 상판만 뜯어내서 아래 위 모두 접을 수 있게 경첩을 달았다. 평면도 상의 공간 차지를 가급적 줄이려던 거라 이 단계가 필수였다. 원래 계획하던 공정은 다 적용했지만 편의성이 좀 아쉬운 부분들이 있어서 나중에 쿨타임 차면 다시 손댈 것 같다. 

 

한편으로는 잡동사니 수납장을 새로 정립해야 한다. 그동안은 이런저런 자잘한 물건들이 쌓이면 무턱대고 버리지는 못하고 박스에 차곡차곡 모아두곤 했는데 1x년째 그러고 있다보니 그런 박스들도 꽤 모여버렸다. 종종 버릴 건 버린다지만 수납 자체도 다시 생각해볼 때가 되었다.

책장이 있던, 눈에 덜 띄고 발길도 덜 가는 공간으로 수납장을 밀어넣고 거기에 이것저것 소코반을 해야 한다.

 

전에 쓰던 책장은 나무 합판을 간단하게 조립한 물건인데 이번에 다 해체해버렸다. 원래는 이사 자주 다닐 때 매번 책을 책장에서 꺼내고 묶고 풀고 다시 넣는 과정을 하지 않으려고 큰 책장 대신 작은 책장을 여러 개 올려서 썼는데 이젠 종이책 자체를 버리면 버렸지 더 늘릴 생각은 없으니 나무 조립식 책장도 버릴 때가 되었다.

풀어낸 합판들은 원래 계획으로는 수납장에 덧대서 나무 느낌을 내볼까 하는 생각도 있었는데 그러기엔 손도 더 가고 나무색과 벽색이 잘 맞을지도 의문이라 버리는 쪽으로 기울고 있다.

2025년 8월 12일 화요일

영상 분석을 AI가 이렇게 잘 해주면

영상 분석이 필요한 개인 프로젝트를 구상한지 오래 되었다. 완결된 뭔가가 나온 적은 없지만 그동안 꾸준히, 자전거로 치자면 뒷바퀴 바퀴살은 무슨 재질로 몇 개나 붙여야 주행에 좋을지, 체인은 어느 정도 팽팽해야 하는지, 핸들은 평평해야 할지 구부려야 할지, 혹시 앞바구니를 당장은 아니지만 나중에는 붙이고 싶은데 미래를 위해 프레임 모양을 어떻게 만들어야 할지 부분부분 구상과 기술 검토는 해왔다.

 

영상 분석에서 초반에 문제가 됐던 건 결국 그 정확도였다. 흥미로 하는 개인 프로젝트고 다뤄야할 영상이 폭발적으로 늘지는 않기 때문에 속도는 크게 중요하지 않았다. 초기에는 TensorFlow에 있는 이미지 분류 같은 걸 돌려서 거기서 나온 텍스트 더미로 그 다음 단계를 어떻게 해야 하나 궁리하는 정도였다. 텍스트 결과물이 만족스럽거나 하진 않았지만 어쨌든 일단 다음 진행단계를 밟아야 유의미한 진척이 생기기 때문에 한 눈 질끈 감고 정확도가 낮으면 나중에 다시 돌아보자고 생각했었다.

그리고 얼마 전 드디어 (나로서는) 놀라운 결과물을 내주는 도구를 찾았다. AI 모델을 로컬에서 실행해주는 ollama에 qwen25vl:7b 모델을 붙여서 일련의 이미지를 먹이면 내가 얻고자 했던 이미지의 특성들을 착착 뽑아준다. 이 비전 모델을 실행하려면 갖춰야될 GPU 메모리 크기가 약간 장벽이지만 원하는 결과물이 나오는 이상 그 정도는 문제가 안 된다.

ollama를 처음 접했을 때 느꼈던 가능성, AI 관련 각종 기술과 용어들을 따라가지 못해서 어쩌지 하는 막막함이 ollama 같은 걸 그냥 블랙박스로 두고 그동안 쭉 해왔던 것처럼 API로 호출해서 잘 활용해주면 업무 현장에 여전히 내 역할이 있겠구나 하는 납득으로 바뀐 그 느낌이 이번에도 비슷하게 온다. 이거라면 예전에 텍스트까지만 수작업으로 직접 구현하고 이미지는 적당한 검사 서비스 제공자를 못 찾아서 결국 포기했던 성인물 검사도 상당한 수준으로 만들어낼 수 있을 것 같다.

 

물론 모든 난관이 한방에 해소된 건 아니다. 영상 분석의 앞뒤로 이어져야 할 프레임 추출 등의 작업들을 위해 MCP를 활용하려고 찾아봤는데 비전 모델이 MCP 연동까지 지원하지 않기도 하고, 요즘 추세가 여러 모델을 혼합해서 쓴다고도 하고, 아예 A2A라고 기능 단위의 에이전트들끼리 통신하는 게 필요하다고도 한다. 아주 단순화해서 이해하자면 사람이 수동으로 하는 걸 MCP라는 이름으로 모델에서 넘기고 필요하면 IPC 같은 게 되게 한다는 거다. 그럴듯하게 들리긴 하는데 아직 필수라고 느껴지진 않아서 일단 수동으로 때워야 할 부분 같다.

또 ollama는 임베딩 기능을 텍스트에만 지원해서 영상으로 다른 비슷한 영상을 찾게 하는 건 다른 구현을 통해야 할 것 같다. ollama 같이 모델 돌려주는 역할을 하는 다른 동네에서는 된다는 얘기가 보이니 찾아보면 해결은 될 것 같다.