블로그 글 추가: 2026-06-30-miles-a-pytorch-native-stack-for-large-scale-llm-rl-post-training, Miles: 대규모 LLM RL 사후 학습을 위한 PyTorch 네이티브 스택 #98
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: PR Preview | |
| on: | |
| pull_request: | |
| types: [opened, synchronize, reopened, closed] | |
| workflow_dispatch: | |
| inputs: | |
| pr_number: | |
| description: 'PR number to build preview for' | |
| required: true | |
| type: number | |
| schedule: | |
| - cron: '0 0 * * 0' # 매주 일요일 자정 (UTC) | |
| permissions: | |
| contents: read | |
| pull-requests: write | |
| concurrency: | |
| group: pr-preview-${{ github.event.pull_request.number || github.event.inputs.pr_number || 'cleanup' }} | |
| cancel-in-progress: true | |
| jobs: | |
| build-and-deploy: | |
| if: github.event_name == 'pull_request' && github.event.action != 'closed' || github.event_name == 'workflow_dispatch' | |
| runs-on: ubuntu-latest | |
| env: | |
| PR_NUMBER: ${{ github.event.pull_request.number || github.event.inputs.pr_number }} | |
| steps: | |
| - name: Checkout PR branch | |
| uses: actions/checkout@v3 | |
| with: | |
| ref: ${{ github.event_name == 'workflow_dispatch' && format('refs/pull/{0}/head', github.event.inputs.pr_number) || '' }} | |
| - name: Set up Node.js | |
| uses: actions/setup-node@v3 | |
| with: | |
| node-version: 22.17.1 | |
| - name: Install node dependencies | |
| run: | | |
| npm install -g yarn | |
| yarn install | |
| - name: Install system dependencies | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y libffi-dev build-essential | |
| - name: Set up Ruby 3.4 | |
| uses: ruby/setup-ruby@v1 | |
| with: | |
| ruby-version: 3.4.5 | |
| bundler-cache: true | |
| - name: Verify bundler and install dependencies | |
| run: | | |
| bundler --version | |
| gem install rake | |
| bundle check || bundle install --jobs 4 --retry 3 | |
| - name: Initialize & update hub submodule | |
| run: | | |
| git submodule deinit -f . && git submodule update --init --recursive | |
| git submodule update --remote | |
| ./_devel/update_hub_submodule.sh | |
| - name: Copy vendor assets | |
| run: make include-yarn-deps | |
| - name: Build Jekyll site | |
| env: | |
| JEKYLL_GITHUB_TOKEN: ${{ secrets.JEKYLL_GITHUB_TOKEN }} | |
| run: | | |
| JEKYLL_ENV=preview bundle exec jekyll build | |
| - name: Deploy to surge.sh | |
| if: github.event_name == 'workflow_dispatch' || github.event.pull_request.head.repo.full_name == github.repository | |
| env: | |
| SURGE_LOGIN: ${{ secrets.SURGE_LOGIN }} | |
| SURGE_TOKEN: ${{ secrets.SURGE_TOKEN }} | |
| run: | | |
| npm install -g surge | |
| surge ./_site pytorchkr-pr-preview-${{ env.PR_NUMBER }}.surge.sh | |
| - name: Comment preview URL on PR | |
| if: success() && (github.event_name == 'workflow_dispatch' || github.event.pull_request.head.repo.full_name == github.repository) | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const prNumber = process.env.PR_NUMBER; | |
| const previewUrl = `https://pytorchkr-pr-preview-${prNumber}.surge.sh`; | |
| const body = [ | |
| '## PR Preview', | |
| '', | |
| '빌드가 완료되었습니다! 아래 링크에서 변경사항을 확인할 수 있습니다.', | |
| '', | |
| `**미리보기**: ${previewUrl}`, | |
| '', | |
| '> 이 미리보기는 PR이 업데이트될 때마다 자동으로 갱신됩니다.', | |
| ].join('\n'); | |
| const { data: comments } = await github.rest.issues.listComments({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: prNumber, | |
| }); | |
| const botComment = comments.find(c => c.body.includes('PR Preview')); | |
| if (botComment) { | |
| await github.rest.issues.updateComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| comment_id: botComment.id, | |
| body, | |
| }); | |
| } else { | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: prNumber, | |
| body, | |
| }); | |
| } | |
| - name: Comment build failure on PR | |
| if: failure() | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const prNumber = process.env.PR_NUMBER; | |
| const runUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`; | |
| const body = [ | |
| '## PR Preview - Build Failed', | |
| '', | |
| `빌드 중 오류가 발생했습니다. [워크플로우 로그](${runUrl})를 확인해주세요.`, | |
| ].join('\n'); | |
| const { data: comments } = await github.rest.issues.listComments({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: prNumber, | |
| }); | |
| const botComment = comments.find(c => c.body.includes('PR Preview')); | |
| if (botComment) { | |
| await github.rest.issues.updateComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| comment_id: botComment.id, | |
| body, | |
| }); | |
| } else { | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: prNumber, | |
| body, | |
| }); | |
| } | |
| cleanup: | |
| if: github.event_name == 'pull_request' && github.event.action == 'closed' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Teardown surge.sh preview | |
| env: | |
| SURGE_LOGIN: ${{ secrets.SURGE_LOGIN }} | |
| SURGE_TOKEN: ${{ secrets.SURGE_TOKEN }} | |
| run: | | |
| npm install -g surge | |
| surge teardown pytorchkr-pr-preview-${{ github.event.pull_request.number }}.surge.sh | |
| - name: Comment cleanup on PR | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const prNumber = context.payload.pull_request.number; | |
| const { data: comments } = await github.rest.issues.listComments({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: prNumber, | |
| }); | |
| const botComment = comments.find(c => c.body.includes('PR Preview')); | |
| if (botComment) { | |
| const body = [ | |
| '## PR Preview', | |
| '', | |
| '~~미리보기가 삭제되었습니다.~~', | |
| '', | |
| '> PR이 닫혀 미리보기가 자동으로 정리되었습니다.', | |
| ].join('\n'); | |
| await github.rest.issues.updateComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| comment_id: botComment.id, | |
| body, | |
| }); | |
| } | |
| stale-cleanup: | |
| if: github.event_name == 'schedule' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/github-script@v7 | |
| env: | |
| SURGE_LOGIN: ${{ secrets.SURGE_LOGIN }} | |
| SURGE_TOKEN: ${{ secrets.SURGE_TOKEN }} | |
| with: | |
| script: | | |
| const { data: pulls } = await github.rest.pulls.list({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| state: 'closed', | |
| sort: 'updated', | |
| direction: 'desc', | |
| per_page: 100, | |
| }); | |
| const now = new Date(); | |
| const threeDaysAgo = new Date(now - 3 * 24 * 60 * 60 * 1000); | |
| const stale = pulls.filter(pr => new Date(pr.closed_at) < threeDaysAgo); | |
| if (stale.length === 0) { | |
| console.log('No stale previews to clean up.'); | |
| return; | |
| } | |
| const prNumbers = stale.map(pr => pr.number); | |
| console.log(`Cleaning up previews for ${prNumbers.length} closed PRs: ${prNumbers.join(', ')}`); | |
| core.exportVariable('STALE_PRS', prNumbers.join(' ')); | |
| - name: Teardown stale surge.sh previews | |
| if: env.STALE_PRS != '' | |
| env: | |
| SURGE_LOGIN: ${{ secrets.SURGE_LOGIN }} | |
| SURGE_TOKEN: ${{ secrets.SURGE_TOKEN }} | |
| run: | | |
| npm install -g surge | |
| for pr in $STALE_PRS; do | |
| echo "Tearing down pytorchkr-pr-preview-${pr}.surge.sh..." | |
| surge teardown "pytorchkr-pr-preview-${pr}.surge.sh" || true | |
| done |