"""full_autonomy 체크포인트(.pt) → 서빙 번들(autonomy_serving.npz) export. dqn_serving 과 동일 패턴: ScoreNet 가중치만 numpy 로 묶어 PyTorch 없이 서빙한다. 행동 특징은 코드(autonomy_actions)가 런타임 생성하므로 번들에는 가중치만 담는다. 실행(호스트, torch 필요): APP_ENV=local python -m tools.export_autonomy_serving """ import os import numpy as np import torch from negotiation.policies.autonomy_actions import ACTION_DIM, EXTRA_STATE_DIM from negotiation.qtable.domain.service.feature_builder import STATE_FEATURE_DIM, TENANT_FEATURE_DIM _HERE = os.path.dirname(os.path.abspath(__file__)) CKPT_PATH = os.path.join(_HERE, "..", "artifacts", "full_autonomy.pt") OUT_PATH = os.path.join(_HERE, "..", "artifacts", "autonomy_serving.npz") STATE_DIM = STATE_FEATURE_DIM + TENANT_FEATURE_DIM + EXTRA_STATE_DIM def main(): sd = torch.load(CKPT_PATH, map_location="cpu") W0, b0 = sd["net.0.weight"].numpy(), sd["net.0.bias"].numpy() W1, b1 = sd["net.2.weight"].numpy(), sd["net.2.bias"].numpy() W2, b2 = sd["net.4.weight"].numpy(), sd["net.4.bias"].numpy() assert W0.shape[1] == STATE_DIM + ACTION_DIM, f"입력 차원 불일치: {W0.shape[1]}" tmp = OUT_PATH + ".tmp" with open(tmp, "wb") as f: np.savez(f, W0=W0, b0=b0, W1=W1, b1=b1, W2=W2, b2=b2, state_dim=STATE_DIM, action_dim=ACTION_DIM) if os.path.exists(OUT_PATH): os.replace(OUT_PATH, OUT_PATH + ".prev") os.replace(tmp, OUT_PATH) print(f"[저장] {os.path.abspath(OUT_PATH)} (state {STATE_DIM} + action {ACTION_DIM})") if __name__ == "__main__": main()