신입에게 구직은 또 하나의 풀타임이다. 매주 수백 건의 공고를 훑고, 같은 자기소개서를 마흔 번 고쳐 쓰고, 두 달쯤 지나면 가고 싶지도 않은 자리에 "생각하느니 넣자"로 손이 간다. Job Searcher는 그 루프 자체를 작은 모델 하나로 끊어보려는 실험이다. 이력서를 던지면 모델이 직접 LinkedIn에 던질 쿼리를 짜고, JobSpy가 공고를 긁어오고, 공고마다 skills, experience, education, industry, seniority 다섯 축의 점수와 한 줄짜리 근거가 토큰 단위로 흘러나온다. 결과는 50개의 긴 리스트가 아니라, 왜 2등이 3등보다 위인지 읽을 수 있는 짧은 명단이다.
구조의 핵심은 교사–학생 디스틸이다. 교사는 DeepSeek V4 Pro, 학생은 Qwen3-8B다. 교사는 라벨 생성기로만 쓰이고 추론 시점에는 사라진다. 코퍼스는 closed loop로 만들어졌다. Divyaamith/Kaggle-Resume 기반의 이력서 2,500장에서 교사가 LinkedIn 모양의 쿼리를 작성하고, 그 쿼리로 JobSpy가 약 1만 건의 공고를 가져오고, 같은 교사가 모든 (이력서, 공고) 쌍을 다섯 축으로 한 문장씩 근거를 달아 라벨링한다. 학생은 Modal의 A100 한 장 위에서 LoRA SFT 두 번, 한 번은 쿼리 생성용, 한 번은 적합도 평가용으로 학습된다. 산출물은 safetensors와 함께 Q4_K_M 양자화 GGUF + LoRA-GGUF 사이드카로 묶여 llama.cpp 서빙 경로에 맞춰진다.
저자가 회고에서 짚는 두 결정이 인상적이다. 첫째, 쿼리 생성과 적합도 평가를 단일 LoRA로 합치니까 포맷이 양쪽으로 새더라는 것이다. 쿼리 자리에 산문이 끼고, 평가 자리에 JSON이 끼었다. 같은 베이스 위에 어댑터 두 개를 두고 호출마다 핫스왑하자 그 부류의 버그가 통째로 사라졌다. 둘째, 학생을 키우는 것보다 교사의 라벨링 프롬프트를 구체화하는 편이 더 컸다. "strong technical match" 대신 "러스트 4년, 공고는 5년 요구" 식으로 비교 기준을 박아두자, 그 습관이 디스틸을 타고 학생에게 따라왔다. 작은 모델로 내려보낼 때 추상적 칭찬은 안 따라오고 구체적 비교는 따라온다는 관찰이다.
서빙 쪽 세부도 가져갈 만하다. 이 Space는 ZeroGPU 위에서 llama-cpp-python을 돌리는데, ZeroGPU가 호출마다 CUDA 컨텍스트를 재활용하기 때문에 모듈 레벨에 Llama 인스턴스를 잡아두면 두 번째 요청에서 죽은 컨텍스트를 들고 있게 된다. 그래서 인스턴스를 @spaces.GPU 안쪽에 둔다. 그리고 한 제출에 대한 모든 공고 평가를 단일 GPU 콜로 묶어, 공고마다 콜드스타트와 프록시 토큰을 새로 사는 비용을 없앤다. 추가로 좋아한 점은 자료 공개 범위다. 라벨 데이터셋, 학습 가중치, GGUF 사이드카뿐 아니라, 이 Space를 만들던 Claude Code 세션의 원본 JSONL 에이전트 트레이스까지 데이터셋으로 올라가 있다. 정리된 사후 회고보다 막다른 골목과 복구가 그대로 남은 일지가 디스틸 워크플로 학습 자료로 더 쓸모 있다는 쪽에 한 표를 던지는 셈이다.