diff --git a/.githooks/pre-commit b/.githooks/pre-commit index 4cddf6d..da6933c 100755 --- a/.githooks/pre-commit +++ b/.githooks/pre-commit @@ -1,38 +1,37 @@ #!/usr/bin/env bash # -# Optimize any image being committed, and generate its gallery thumbnail. +# Check data/ and optimize any image being committed. # # Enable with: tools/setup-hooks.sh # Bypass once: git commit --no-verify # -# Photos are resized, re-encoded and stripped of EXIF before they enter git -# history, so the repository never stores a 10MB camera original. Rules live in -# tools/optimize-images.sh. +# 1. tools/build.py --check validates every database in data/ (file names, +# required fields, missing photos), so a broken entry never reaches main. +# 2. Photos are resized, re-encoded and stripped of EXIF before they enter git +# history, so the repository never stores a 10MB camera original. Rules live +# in tools/optimize-images.sh. Gallery thumbnails are made by the build and +# are not committed. +# +# Written for the bash 3.2 that ships with macOS. set -euo pipefail REPO="$(git rev-parse --show-toplevel)" cd "$REPO" -# news.html is the source of truth for Recent News on the home page. If it is -# being committed, regenerate the home page's copy so links added on the News -# page never go missing there. -if git diff --cached --name-only --diff-filter=ACMR | grep -qx 'news.html'; then - if tools/sync-home-news.py >/dev/null; then - if ! git diff --quiet -- index.html; then - git add -- index.html - echo "pre-commit: synced Recent News on index.html from news.html" - fi - else - echo "pre-commit: tools/sync-home-news.py failed; commit anyway with --no-verify" >&2 - exit 1 - fi +if ! python3 tools/build.py --check >/dev/null; then + python3 tools/build.py --check || true + echo "pre-commit: fix the data errors above, or commit anyway with --no-verify" >&2 + exit 1 fi # Staged images in the directories the optimizer manages. -mapfile -t STAGED < <( +STAGED=() +while IFS= read -r path; do + STAGED+=("$path") +done < <( git diff --cached --name-only --diff-filter=ACMR \ - | grep -Ei '^(stories|lab_photo|group_profile|research_focus)/.*\.(jpe?g|png)$' || true + | grep -Ei '^(data/gallery/photos|data/members/photos|static/research_focus|static/lab_photo)/.*\.(jpe?g|png)$' || true ) [ "${#STAGED[@]}" -gt 0 ] || exit 0 @@ -52,12 +51,10 @@ EOF exit 1 fi -# Remember the HTML files' state so we can stage only the ones the optimizer -# actually rewrites (when a file changes extension). -declare -A HTML_BEFORE -for page in *.html; do - [ -f "$page" ] && HTML_BEFORE["$page"]="$(git hash-object "$page")" -done +# Files that name images. Remember their state so we stage only the ones the +# optimizer rewrites (when an image changes extension), not unrelated edits. +REFS="data/gallery/gallery.yaml data/members/members.yaml layout/index.html content/research.html" +BEFORE="$(for f in $REFS; do [ -f "$f" ] && echo "$f $(git hash-object "$f")"; done)" echo "pre-commit: optimizing ${#STAGED[@]} image(s)..." tools/optimize-images.sh "${STAGED[@]}" @@ -75,32 +72,10 @@ for path in "${STAGED[@]}"; do fi done -# A new photo in stories/ needs its gallery thumbnail generated and staged. -for path in "${STAGED[@]}"; do - case "$path" in - stories/thumbs/*) continue ;; - stories/*) ;; - *) continue ;; - esac - base="$(basename "${path%.*}")" - thumb="stories/thumbs/${base}.jpg" - if [ ! -f "$thumb" ]; then - source_file="$path" - [ -f "$source_file" ] || source_file="${path%.*}.jpg" - convert "$source_file" -auto-orient -resize "600x600^" -gravity center \ - -extent 600x600 -strip -interlace Plane -sampling-factor 4:2:0 \ - -quality 80 -set comment "log-opt-v1" "$thumb" - chmod 644 "$thumb" - echo " generated thumbnail $thumb" - fi - git add -- "$thumb" -done - -# Stage HTML only where a path reference was rewritten. -for page in "${!HTML_BEFORE[@]}"; do - [ -f "$page" ] || continue - if [ "$(git hash-object "$page")" != "${HTML_BEFORE[$page]}" ]; then - git add -- "$page" - echo " staged rewritten references in $page" +for f in $REFS; do + [ -f "$f" ] || continue + if [ "$(echo "$BEFORE" | awk -v f="$f" '$1 == f { print $2 }')" != "$(git hash-object "$f")" ]; then + git add -- "$f" + echo " staged rewritten references in $f" fi done diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..7f5727a --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,56 @@ +# Build the site from data/ + layout/ and publish it to GitHub Pages. +# +# Requires Settings -> Pages -> Build and deployment -> Source: "GitHub Actions". +# Pull requests are built (so broken data fails the check) but not deployed. +name: Build and deploy + +on: + push: + branches: [main] + pull_request: + workflow_dispatch: + +permissions: + contents: read + pages: write + id-token: write + +# Never let two deployments race for the Pages environment. +concurrency: + group: pages + cancel-in-progress: false + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + cache: pip + + - name: Install dependencies + run: | + sudo apt-get update -q + sudo apt-get install -y -q imagemagick + pip install -r requirements.txt + + - name: Build + run: python tools/build.py + + - uses: actions/upload-pages-artifact@v3 + with: + path: _site + + deploy: + if: github.event_name != 'pull_request' + needs: build + runs-on: ubuntu-latest + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - id: deployment + uses: actions/deploy-pages@v4 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ed3900c --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +_site/ +__pycache__/ +.DS_Store diff --git a/CLAUDE.md b/CLAUDE.md index 1ed0db0..87547f8 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,69 +4,122 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co ## Overview -This is a static HTML/CSS academic lab website for the **Lee Optimization Group (LOG)** at POSTECH, deployed via GitHub Pages at `opt.postech.ac.kr`. There is no build step — edit HTML files directly and push to deploy. +Academic lab website for the **Lee Optimization Group (LOG)** at POSTECH, served by +GitHub Pages at `opt.postech.ac.kr`. **Content and design are separate**: content is +data in `data/`, design is Jinja templates in `layout/`, and `tools/build.py` +renders them into `_site/`. `.github/workflows/deploy.yml` builds and deploys on every +push to `main` (Pages source must be "GitHub Actions"). `_site/` is output — never edit +it, never commit it. The root `README.md` maps every kind of edit to its file. + +```bash +python3 tools/build.py # build into _site/ +python3 tools/build.py --check # validate data/ only (exit 1 on errors) +python3 tools/build.py --serve # build and serve on http://localhost:8000 +``` ## Pages -| File | Purpose | -|---|---| -| `index.html` | Home — lab intro, news, acknowledgements | -| `news.html` | Full news archive (linked from the home page) | -| `research.html` | Research areas | -| `publications.html` | Paper list | -| `group.html` | Lab members | -| `hiring.html` | Open positions | -| `contact.html` | Contact info | -| `gallery.html` | Lab photos | +| Template | Purpose | Data | +|---|---|---| +| `layout/index.html` | Home — lab intro, recent news | `data/news/` | +| `layout/news.html` | News list (linked from the home page) | `data/news/` | +| `content/research.html` | Research areas | — | +| `layout/publications.html` | Paper list | `data/publications/` | +| `layout/group.html` | Members, gallery preview, alumni | `data/members/`, `data/gallery/` | +| `layout/gallery.html` | Lab photos | `data/gallery/` | +| `layout/photo.html` | Single-photo viewer (`photo.html?image=stories/…`) | — | +| `content/hiring.html` | Open positions | — | +| `content/contact.html` | Contact info | — | + +Pages built from data have their own layout in `layout/`. Pages that are just text +(`content/*.html`) are plain HTML for inside `
` — no template code — wrapped by +`layout/page.html`; their CSS is `layout/styles/.css` and any extra script +`layout/scripts/.js` (after the shared `layout/scripts/drawer.js`). Their title +is their nav label in `data/site.toml`. Every page extends `layout/base.html`, which +holds the head, header/nav, footer and mobile drawer. Nav items, footer logos and the lab name come from `data/site.toml`. + +## Content lives in `data/` — never in templates + +Each directory in `data/` is one database: its entries, a `config.toml` of +presentation settings, and a `README.md` on how to add an entry. **Read that README +before adding or changing content.** + +| Database | Entries | Notes | +|---|---|---| +| `data/news/` | `.yaml`: items keyed by idx (`39:`) with `date`, `text` (one paragraph of Markdown), optional `paper` | Higher idx = newer, unique across years; a repeated key is an error. Home page shows the newest `home.count`. | +| `data/publications/` | one `papers.bib`; entry order = display order within a heading | Headings: `is_preprint`, then `year` (newest first), then `is_previous`. News refers to entries as `@key` (`@lee26fresco`). | +| `data/members/` | `members.yaml` (`members:` and `alumni:`, each person keyed by tag) + `photos/` | The tag is the person's id everywhere (`@tag`). | +| `data/gallery/` | `gallery.yaml` (photos keyed by idx, highest first) + `photos/` | Thumbnails are generated by the build; never commit thumbnails. | -## Derived content — never edit these by hand +The build validates all of it (file names, required fields, unknown fields, missing +photos) and refuses to build on errors. -Two blocks on the site are **copies of content that lives elsewhere**. Editing the -copy instead of the source is how links go missing. +Numbers that used to be hard-coded in page scripts (news shown/added per click, home +news count, gallery preview page size) come from the +`config.toml` files — change them there, not in the template. -| Block | Source of truth | Kept in sync by | -|---|---|---| -| `index.html` → Recent News (newest 5) | `news.html` | `tools/sync-home-news.py` + pre-commit hook, and a runtime `fetch` in `index.html` | -| `group.html` → Gallery preview (newest 5) | `gallery.html` | runtime `fetch` in `group.html` | +Rendering rules applied by the build: +- Markdown links to `http(s)://` get `target="_blank" rel="noopener"`; relative links + stay in the same tab. The same holds for paper links. +- Text is HTML-escaped. -**Add news to `news.html` only.** The home page follows automatically: +Published paths are stable public URLs and must not change: gallery photos are served +at `stories/`, thumbnails at `stories/thumbs/`, member photos at `group_profile/` +(see `PUBLISH` in `tools/build.py`). `static/` is copied to the site root as is, so +`static/research_focus/` is served at `research_focus/`. -```bash -tools/sync-home-news.py # rewrite index.html's copy -tools/sync-home-news.py --check # report drift, change nothing (exit 1) -``` +### People (`@tags`) and member faces + +Every data file starts with a comment listing every field of an entry. -Committing `news.html` runs the sync through the pre-commit hook and stages -`index.html` alongside it. On top of that, both pages re-read their source at load -time, so a stale copy still renders correctly for visitors — the static markup is -only the no-JS fallback. Commented-out entries lower down `index.html`'s news list -are left alone by the sync. +Every person in `data/members/members.yaml` (members and alumni) is keyed by their tag. News +text and BibTeX `author` fields refer to lab people **only** by tag (`@hyunjijung`); +plain names are plain text. In news a tag renders as the person's `short` name +(default: first name), in author lists as the full `name`. Unknown tags fail the build. -Gallery photos work the same way: add the tile to `gallery.html` and the preview on -`group.html` follows. Do not hand-edit either preview block. +`tools/build.py` renders small rounded-square faces of **current members** at build +time (alumni tags give the name without a face): + +- **News** (news page, home page): under each item's date — the in-lab authors of the + papers it refers to — its `paper` field, then links written `[paper](@shin23closer)`, which the + build points at the entry's `paper` URL — then members + `@tagged` in the text; up to `faces.max` (3), then `+N`. +- **Publications**: before each tagged current member in `.paper-authors`. + +Nothing is inferred from names or links. Each face links to the member's `url` (new +tab) if set, otherwise to `group.html#`; `group.html` assigns those +ids at load time. ## Architecture -**CSS is inline per-page** — each HTML file contains a full ` - - - -
- -
- Lee Optimization Group - - -
- -
-
-

- Computational Optimization Lab
-
- (Office) RIST 4, Room #4427
-
- Department of Computer Science and Engineering
- Pohang University of Science and Technology (POSTECH)
- 67 Cheongam-ro, Nam-gu, Pohang-si, Gyeongsangbuk-do (37673)
- South Korea -

- -
- -
-
-
- -
- © 2026 LOG. All rights reserved. - -
- -
- - - - - - - diff --git a/content/contact.html b/content/contact.html new file mode 100644 index 0000000..21a4ebc --- /dev/null +++ b/content/contact.html @@ -0,0 +1,21 @@ +
+

+ Computational Optimization Lab
+
+ (Office) RIST 4, Room #4427
+
+ Department of Computer Science and Engineering
+ Pohang University of Science and Technology (POSTECH)
+ 67 Cheongam-ro, Nam-gu, Pohang-si, Gyeongsangbuk-do (37673)
+ South Korea +

+ +
+ +
+
diff --git a/content/hiring.html b/content/hiring.html new file mode 100644 index 0000000..cd0f64e --- /dev/null +++ b/content/hiring.html @@ -0,0 +1,67 @@ +

Lab Documentary

+
+ +
+ +

Note on Hiring

+

+ Thank you for your interest in joining our group. We are always on the lookout for highly motivated and talented people to work on optimization. + Please review carefully the guidance below and submit this application form if you're applying for a position. + While we encourage you to contact us with any inquiries, please be aware that due to the high volume of emails, + we may not be able to offer individual responses. + Common questions for joining our lab are answered here — we encourage you to check it out first. +

+ +

Internship

+

+ We highly recommend all prospective students to participate in an internship with us before formally applying to the graduate program. + While direct application is an option, pursuing an internship first is the most common route for successful applicants: +

+
    +
  • It allows us to get to know each other and lets you experience the lab firsthand.
  • +
  • We rarely hire students we haven't worked with before.
  • +
  • The POSTECH admission process is highly competitive, and a positive internship record can significantly strengthen your application.
  • +
+

+ Summer and Winter internships are available. All internships start at the beginning of off-term times + (mid-June for Summer, early-January for Winter) and are expected to last at least two months (ideally longer). + Applications are accepted until one month prior to the start of each internship + (i.e., May for Summer and November for Winter). + Selected candidates will be invited for an interview shortly after the deadline. +

+ +

+ 🔔 + Applications for the 27 Winter internship will be received from 2 November 2026 to 29 November 2026. + +

+ +

Graduate Program

+

+ We take on a few new graduate students each year. Please apply directly to either the AI or CSE department, + and mention our PI in your application. Once you pass the screening phase and reach the advisor-matching stage, + please email PI (namhoon.lee@postech.ac.kr) and also submit this form. +

+ +

Postdoc

+

+ We're actively looking for postdocs who are interested in advising students with us in our lab. + If you have completed your PhD recently or are finishing it soon, please feel free to email PI. +

+ +

All

+

+ Please submit this application form. +

diff --git a/content/research.html b/content/research.html new file mode 100644 index 0000000..cc87419 --- /dev/null +++ b/content/research.html @@ -0,0 +1,115 @@ +

Research Focus

+ +
+ Model Compression +
+

+ Model Compression + As AI models become larger and more computationally demanding, compression is essential for making them practical and sustainable. + We develop optimization methods that reduce training and inference costs while preserving model performance. + Our work focuses on extreme compression across the AI model lifecycle. + +

+
+
+ Extreme Sparsity (EMNLP '26, + ICLR '26, + ICML '25 Spotlight, + EMNLP '24) + We push the limits of LLM sparsity through advanced optimization frameworks such as ADMM, aiming for robust generalization even at extreme compression levels. +
+
+ Joint Compression + We develop unified frameworks that integrate pruning, quantization, and distillation to reduce error accumulation and preserve core model capabilities, including reasoning, in highly compressed states. +
+
+
+
+ + +
+ Distributed Training +
+

+ Distributed Training + The shift toward massive-scale models has made distributed training essential for workloads that no single machine can support. + From an optimization perspective, the key challenge is maintaining efficiency while managing the overhead of communication between devices. + Our research develops robust algorithms for decentralized and high-latency training environments. + +

+
+
+ Communication-Efficient Training (NeurIPS '26 WS) + We design methods that reduce the heavy data-sharing requirements of distributed training through information compression, reduced synchronization, and zeroth-order optimization. +
+
+ Asynchronous Training (NeurIPS '26, + ICML '26) + We create algorithms that allow devices to work independently and reduce waiting time, with a focus on correcting errors caused by slightly outdated information. +
+
+ Device-Heterogeneous Training + We develop adaptive strategies for imbalanced computational resources across devices, aiming for consistent global convergence despite variations in hardware performance. +
+
+
+
+ + + + + +
+ Advanced Optimization +
+

+ Advanced Optimization + Optimization has long been a source of crucial ideas that drastically enhance all corners of deep neural network training, and many of its most impactful questions remain open. + Our research investigates these questions and develops optimization principles and algorithms for modern deep learning systems. + +

+
+
+ Optimization for Generalization (UAI '25, + ICML '25) + We study the role of loss landscape curvature in generalization and develop flatness-oriented optimization strategies with both theoretical advantages and scalability for modern deep neural networks. +
+
+ Zeroth-Order Optimization for Black-Box Model Training (ICLR '25) + We develop methods that operate without direct gradient access, motivated by settings such as proprietary model APIs and decentralized learning systems where gradients are unavailable. +
+
+
+
diff --git a/data/gallery/README.md b/data/gallery/README.md new file mode 100644 index 0000000..6c734c3 --- /dev/null +++ b/data/gallery/README.md @@ -0,0 +1,44 @@ +# Gallery + +`gallery.yaml` lists the photos on the Gallery page, each under its number +(idx), and `photos/` holds the photos themselves. Higher numbers are newer and +come first. The square thumbnails in the grid are +**generated by the build** from each photo, so you never make them yourself. + +## Add a photo + +1. Put the photo in `photos/`. Use a straight-from-the-camera original; the + pre-commit hook shrinks it to 2560px and removes EXIF (including GPS + location) when you commit. +2. Add it at the top of `gallery.yaml` with the next number (highest + 1), + and a blank line before the next photo: + + ```yaml + 46: + file: 26_aiconnect.jpg + caption: AI Connect Korea 2026 - IITP President's Award + alt: Kwanhee receiving the IITP President's Award at AI Connect Korea 2026 + ``` + +| Field | Required | Notes | +|---|---|---| +| `46:` (the key) | yes | The photo's idx. Sets the order: higher is newer. Never reuse one; to reorder two photos, swap their numbers. | +| `file` | yes | File name in `photos/`. Lowercase, no spaces. | +| `caption` | yes | Shown when hovering a tile, and under the photo when opened. | +| `alt` | no | Description for screen readers, if it should differ from `caption`. | + +The Members page shows the first few photos as a preview, and the photo viewer's +previous/next buttons follow the same order. Both update automatically. + +## Thumbnails + +The build crops each photo to a centered square (`thumbnail.size` px) and saves +it as `stories/thumbs/.jpg` in the built site. If a crop cuts off what +matters, crop the photo itself before adding it. + +## Presentation settings (`config.toml`) + +| Setting | Meaning | +|---|---| +| `thumbnail.size` / `thumbnail.quality` | Thumbnail size in px / JPEG quality | +| `preview.count` | Photos per page in the Members page preview | diff --git a/data/gallery/config.toml b/data/gallery/config.toml new file mode 100644 index 0000000..ac3ead0 --- /dev/null +++ b/data/gallery/config.toml @@ -0,0 +1,9 @@ +# Gallery settings. Details: README.md. + +[thumbnail] +size = 600 # px, square center crop made at build time +quality = 80 # JPEG quality + + +[preview] +count = 5 # photos per page in the Members page preview diff --git a/data/gallery/gallery.yaml b/data/gallery/gallery.yaml new file mode 100644 index 0000000..dff6062 --- /dev/null +++ b/data/gallery/gallery.yaml @@ -0,0 +1,193 @@ +# Gallery photos, shown highest number first (newest). How to add one: README.md. +# +# Every field of a photo (the photo's key is its idx): +# +# 46: # idx, required. Unique; higher = newer (sets the order) +# file: 26_acl.jpg # required. File in photos/ +# caption: ACL 2026 presentation # required. Hover text and caption in the photo viewer +# alt: ... # optional. Description for screen readers (default: caption) + +45: + file: 26_aiconnect.jpg + caption: AI Connect Korea 2026 - IITP President's Award + alt: Kwanhee receiving the IITP President's Award at AI Connect Korea 2026 + +44: + file: 26_ckaia.jpg + caption: CKAIA 2026 - Presentation + alt: Presenting our work at CKAIA 2026 + +43: + file: 26_pl_workshop.jpg + caption: Protocol learning workshop + +42: + file: 26_pl_workshop2.jpg + caption: Protocol learning workshop + +41: + file: 26_das_dinner.jpg + caption: LOG-DASLab group dinner + +40: + file: 26_icml_poster.jpg + caption: ICML 2026 poster + +39: + file: 26_icml.jpg + caption: ICML 2026 + +38: + file: 26_acl.jpg + caption: ACL 2026 presentation + +37: + file: 26_kcc_conference.jpg + caption: KCC 2026 + +36: + file: 26_kcc_cliff.jpg + caption: KCC 2026 - Jeju + alt: KCC 2026 Jeju coast + +35: + file: 26_kcc_yaut.jpg + caption: KCC 2026 - Yacht Tour + alt: KCC 2026 yacht tour + +34: + file: 26_lovelog.jpg + caption: Love LOG + +33: + file: 26_iclrdinner.jpg + caption: ICLR Dinner + +32: + file: 26_iclr.jpg + caption: ICLR 2026 + +31: + file: 26_logdinner.jpg + caption: LOG Dinner + +30: + file: 26_bowling.jpg + caption: Bowling Night + +29: + file: 26_post-icml.jpg + caption: post-ICML 2026 + alt: Post-ICML 2026 + +28: + file: 25_yearendparty.jpg + caption: Year-end Party + +27: + file: 25_boardgame.jpg + caption: Board Game Night + +26: + file: 25_openday.jpg + caption: LOG Open Day + +25: + file: 25_readinggroup.jpg + caption: Reading Group + +24: + file: 25_icml.jpg + caption: ICML 2025 + +23: + file: 25_postech.jpg + caption: POSTECH + +22: + file: 25_noscreenday.jpg + caption: No Screen Day + +21: + file: 25_iclr.jpg + caption: ICLR 2025 + +20: + file: 24_effl-log.jpg + caption: EFFL and LOG Day + +19: + file: 24_logspringouting.jpg + caption: LOG Spring Outing + +18: + file: 24_logspring.jpg + caption: LOG Spring Outing + +17: + file: 24_logesports3.jpg + caption: LOG e-sports + +16: + file: 24_logesports2.jpg + caption: LOG e-sports + +15: + file: 24_logesports.jpg + caption: LOG e-sports + +14: + file: 23_christmasparty.jpg + caption: Christmas Party + +13: + file: 23_cake.jpg + caption: LOG Cake + +12: + file: 23_santa.jpg + caption: Santa Dongyeop + +11: + file: 23_jkaia.jpg + caption: JKAIA Best Paper Award + +10: + file: 23_postech.jpg + caption: POSTECH + +9: + file: 23_movienight.jpg + caption: Movie Night + +8: + file: 23_halonabeachcove.jpg + caption: Halona Beach Cove, Hawaii + +7: + file: 23_icml3.jpg + caption: Makapu'u Lookout, Hawaii + +6: + file: 23_icml2.jpg + caption: ICML 2023 + +5: + file: 23_icml.jpg + caption: ICML 2023 + +4: + file: 23_boardgame.jpg + caption: Board Game Night + +3: + file: 23_teachersday.jpg + caption: Teacher's Day + +2: + file: 23_logday.jpg + caption: LOG Day + +1: + file: 23_noscreenday.jpg + caption: No Screen Day diff --git a/stories/23_boardgame.jpg b/data/gallery/photos/23_boardgame.jpg similarity index 100% rename from stories/23_boardgame.jpg rename to data/gallery/photos/23_boardgame.jpg diff --git a/stories/23_cake.jpg b/data/gallery/photos/23_cake.jpg similarity index 100% rename from stories/23_cake.jpg rename to data/gallery/photos/23_cake.jpg diff --git a/stories/23_christmasparty.jpg b/data/gallery/photos/23_christmasparty.jpg similarity index 100% rename from stories/23_christmasparty.jpg rename to data/gallery/photos/23_christmasparty.jpg diff --git a/stories/23_halonabeachcove.jpg b/data/gallery/photos/23_halonabeachcove.jpg similarity index 100% rename from stories/23_halonabeachcove.jpg rename to data/gallery/photos/23_halonabeachcove.jpg diff --git a/stories/23_icml.jpg b/data/gallery/photos/23_icml.jpg similarity index 100% rename from stories/23_icml.jpg rename to data/gallery/photos/23_icml.jpg diff --git a/stories/23_icml2.jpg b/data/gallery/photos/23_icml2.jpg similarity index 100% rename from stories/23_icml2.jpg rename to data/gallery/photos/23_icml2.jpg diff --git a/stories/23_icml3.jpg b/data/gallery/photos/23_icml3.jpg similarity index 100% rename from stories/23_icml3.jpg rename to data/gallery/photos/23_icml3.jpg diff --git a/stories/23_jkaia.jpg b/data/gallery/photos/23_jkaia.jpg similarity index 100% rename from stories/23_jkaia.jpg rename to data/gallery/photos/23_jkaia.jpg diff --git a/stories/23_logday.jpg b/data/gallery/photos/23_logday.jpg similarity index 100% rename from stories/23_logday.jpg rename to data/gallery/photos/23_logday.jpg diff --git a/stories/23_movienight.jpg b/data/gallery/photos/23_movienight.jpg similarity index 100% rename from stories/23_movienight.jpg rename to data/gallery/photos/23_movienight.jpg diff --git a/stories/23_noscreenday.jpg b/data/gallery/photos/23_noscreenday.jpg similarity index 100% rename from stories/23_noscreenday.jpg rename to data/gallery/photos/23_noscreenday.jpg diff --git a/stories/23_postech.jpg b/data/gallery/photos/23_postech.jpg similarity index 100% rename from stories/23_postech.jpg rename to data/gallery/photos/23_postech.jpg diff --git a/stories/23_santa.jpg b/data/gallery/photos/23_santa.jpg similarity index 100% rename from stories/23_santa.jpg rename to data/gallery/photos/23_santa.jpg diff --git a/stories/23_teachersday.jpg b/data/gallery/photos/23_teachersday.jpg similarity index 100% rename from stories/23_teachersday.jpg rename to data/gallery/photos/23_teachersday.jpg diff --git a/stories/24_effl-log.jpg b/data/gallery/photos/24_effl-log.jpg similarity index 100% rename from stories/24_effl-log.jpg rename to data/gallery/photos/24_effl-log.jpg diff --git a/stories/24_logesports.jpg b/data/gallery/photos/24_logesports.jpg similarity index 100% rename from stories/24_logesports.jpg rename to data/gallery/photos/24_logesports.jpg diff --git a/stories/24_logesports2.jpg b/data/gallery/photos/24_logesports2.jpg similarity index 100% rename from stories/24_logesports2.jpg rename to data/gallery/photos/24_logesports2.jpg diff --git a/stories/24_logesports3.jpg b/data/gallery/photos/24_logesports3.jpg similarity index 100% rename from stories/24_logesports3.jpg rename to data/gallery/photos/24_logesports3.jpg diff --git a/stories/24_logspring.jpg b/data/gallery/photos/24_logspring.jpg similarity index 100% rename from stories/24_logspring.jpg rename to data/gallery/photos/24_logspring.jpg diff --git a/stories/24_logspringouting.jpg b/data/gallery/photos/24_logspringouting.jpg similarity index 100% rename from stories/24_logspringouting.jpg rename to data/gallery/photos/24_logspringouting.jpg diff --git a/stories/25_boardgame.jpg b/data/gallery/photos/25_boardgame.jpg similarity index 100% rename from stories/25_boardgame.jpg rename to data/gallery/photos/25_boardgame.jpg diff --git a/stories/25_iclr.jpg b/data/gallery/photos/25_iclr.jpg similarity index 100% rename from stories/25_iclr.jpg rename to data/gallery/photos/25_iclr.jpg diff --git a/stories/25_icml.jpg b/data/gallery/photos/25_icml.jpg similarity index 100% rename from stories/25_icml.jpg rename to data/gallery/photos/25_icml.jpg diff --git a/stories/25_noscreenday.jpg b/data/gallery/photos/25_noscreenday.jpg similarity index 100% rename from stories/25_noscreenday.jpg rename to data/gallery/photos/25_noscreenday.jpg diff --git a/stories/25_openday.jpg b/data/gallery/photos/25_openday.jpg similarity index 100% rename from stories/25_openday.jpg rename to data/gallery/photos/25_openday.jpg diff --git a/stories/25_postech.jpg b/data/gallery/photos/25_postech.jpg similarity index 100% rename from stories/25_postech.jpg rename to data/gallery/photos/25_postech.jpg diff --git a/stories/25_readinggroup.jpg b/data/gallery/photos/25_readinggroup.jpg similarity index 100% rename from stories/25_readinggroup.jpg rename to data/gallery/photos/25_readinggroup.jpg diff --git a/stories/25_yearendparty.jpg b/data/gallery/photos/25_yearendparty.jpg similarity index 100% rename from stories/25_yearendparty.jpg rename to data/gallery/photos/25_yearendparty.jpg diff --git a/stories/26_acl.jpg b/data/gallery/photos/26_acl.jpg similarity index 100% rename from stories/26_acl.jpg rename to data/gallery/photos/26_acl.jpg diff --git a/stories/26_aiconnect.jpg b/data/gallery/photos/26_aiconnect.jpg similarity index 100% rename from stories/26_aiconnect.jpg rename to data/gallery/photos/26_aiconnect.jpg diff --git a/stories/26_bowling.jpg b/data/gallery/photos/26_bowling.jpg similarity index 100% rename from stories/26_bowling.jpg rename to data/gallery/photos/26_bowling.jpg diff --git a/stories/26_ckaia.jpg b/data/gallery/photos/26_ckaia.jpg similarity index 100% rename from stories/26_ckaia.jpg rename to data/gallery/photos/26_ckaia.jpg diff --git a/stories/26_das_dinner.jpg b/data/gallery/photos/26_das_dinner.jpg similarity index 100% rename from stories/26_das_dinner.jpg rename to data/gallery/photos/26_das_dinner.jpg diff --git a/stories/26_iclr.jpg b/data/gallery/photos/26_iclr.jpg similarity index 100% rename from stories/26_iclr.jpg rename to data/gallery/photos/26_iclr.jpg diff --git a/stories/26_iclrdinner.jpg b/data/gallery/photos/26_iclrdinner.jpg similarity index 100% rename from stories/26_iclrdinner.jpg rename to data/gallery/photos/26_iclrdinner.jpg diff --git a/stories/26_icml.jpg b/data/gallery/photos/26_icml.jpg similarity index 100% rename from stories/26_icml.jpg rename to data/gallery/photos/26_icml.jpg diff --git a/stories/26_icml_poster.jpg b/data/gallery/photos/26_icml_poster.jpg similarity index 100% rename from stories/26_icml_poster.jpg rename to data/gallery/photos/26_icml_poster.jpg diff --git a/stories/26_kcc_cliff.jpg b/data/gallery/photos/26_kcc_cliff.jpg similarity index 100% rename from stories/26_kcc_cliff.jpg rename to data/gallery/photos/26_kcc_cliff.jpg diff --git a/stories/26_kcc_conference.jpg b/data/gallery/photos/26_kcc_conference.jpg similarity index 100% rename from stories/26_kcc_conference.jpg rename to data/gallery/photos/26_kcc_conference.jpg diff --git a/stories/26_kcc_yaut.jpg b/data/gallery/photos/26_kcc_yaut.jpg similarity index 100% rename from stories/26_kcc_yaut.jpg rename to data/gallery/photos/26_kcc_yaut.jpg diff --git a/stories/26_logdinner.jpg b/data/gallery/photos/26_logdinner.jpg similarity index 100% rename from stories/26_logdinner.jpg rename to data/gallery/photos/26_logdinner.jpg diff --git a/stories/26_lovelog.jpg b/data/gallery/photos/26_lovelog.jpg similarity index 100% rename from stories/26_lovelog.jpg rename to data/gallery/photos/26_lovelog.jpg diff --git a/stories/26_pl_workshop.jpg b/data/gallery/photos/26_pl_workshop.jpg similarity index 100% rename from stories/26_pl_workshop.jpg rename to data/gallery/photos/26_pl_workshop.jpg diff --git a/stories/26_pl_workshop2.jpg b/data/gallery/photos/26_pl_workshop2.jpg similarity index 100% rename from stories/26_pl_workshop2.jpg rename to data/gallery/photos/26_pl_workshop2.jpg diff --git a/stories/26_post-icml.jpg b/data/gallery/photos/26_post-icml.jpg similarity index 100% rename from stories/26_post-icml.jpg rename to data/gallery/photos/26_post-icml.jpg diff --git a/data/members/README.md b/data/members/README.md new file mode 100644 index 0000000..58654be --- /dev/null +++ b/data/members/README.md @@ -0,0 +1,54 @@ +# Members + +`members.yaml` lists the people on the Members page, and `photos/` holds their +headshots. + +## Add a member + +1. Put their photo in `photos/`, named after them without spaces: + `photos/hyeondojang.jpg`. Any size is fine; the pre-commit hook shrinks + it to 512px (see the repository README). +2. Add them under `members:` in `members.yaml`, at the position they should + appear, keyed by their tag: + + ```yaml + hyeondojang: + name: Hyeondo Jang + photo: hyeondojang.jpg + position: MS Student + url: https://hyeondo-jang.github.io/ # optional: name links here + ``` + +| Field | Required | Notes | +|---|---|---| +| `hyeondojang:` (the key) | yes | Their tag: how news and papers refer to them, `@hyeondojang`. Lowercase, no spaces; use the photo's name. Never reuse one. | +| `name` | yes | As shown under the photo and in author lists. | +| `short` | no | Name used for `@tag` in news. Defaults to the first word of `name`. | +| `photo` | yes | File name in `photos/`. Also the anchor of their card: `group.html#hyeondojang`. | +| `position` | yes | `Principal Investigator`, `PhD Student`, `MS/PhD Student`, `MS Student`, … | +| `url` | no | Personal site. Without it the name is plain text. | +| `alt` | no | Photo description, if it should differ from `name`. | + +## When someone graduates + +Remove them from `members:`, and add them under `alumni:`: + +```yaml + jaehyeonjeong: # keep the same tag, so old news and papers still work + name: Jaehyeon Jeong + url: https://... # optional + note: (MS 2026 → ETRI) +``` + +Their `@tag` keeps working in news and papers, showing the name without a face. +Keep their photo in `photos/`, since old pages may still link to it. Someone who +stays on (MS → PhD) can be in both lists under one tag. + +## Presentation settings (`config.toml`) + +| Setting | Meaning | +|---|---| +| `alumni.show` | Show the Alumni list | +| `alumni.heading` | Its heading | + +The gallery preview on the Members page is configured in `data/gallery/`. diff --git a/data/members/config.toml b/data/members/config.toml new file mode 100644 index 0000000..ebf6853 --- /dev/null +++ b/data/members/config.toml @@ -0,0 +1,5 @@ +# Members page settings. Details: README.md. + +[alumni] +show = true # show the Alumni list +heading = "Alumni" diff --git a/data/members/members.yaml b/data/members/members.yaml new file mode 100644 index 0000000..e3c8f9a --- /dev/null +++ b/data/members/members.yaml @@ -0,0 +1,120 @@ +# Lab members and alumni, in display order. How to add someone: README.md. +# +# Every field of a person (the person's key is their tag, used as @hyunjijung +# in news and papers.bib; never reuse one): +# +# members: +# hyunjijung: +# name: Hyunji Jung # required. Shown under the photo and in author lists +# short: Hyunji # optional. Name for @tag in news (default: first word of name) +# photo: hyunjijung.jpg # required. File in photos/; also the card anchor group.html#hyunjijung +# position: MS Student # required. Shown under the name +# url: https://... # optional. Personal site; name and faces link here +# alt: Hyunji Jung # optional. Photo description (default: name) +# +# alumni: +# jaehyeonjeong: # keep the tag they had as a member +# name: Jaehyeon Jeong # required +# url: https://... # optional +# note: (MS 2026 → ETRI) # optional. Shown after the name + +members: + + namhoonlee: + name: Namhoon Lee + photo: namhoonlee.jpg + position: Principal Investigator + url: https://namhoonlee.github.io/ + + damiso: + name: Dami So + photo: damiso.jpg + position: Administrator + + jinseokchung: + name: Jinseok Chung + photo: jinseokchung.jpg + position: MS/PhD Student + + donghyunoh: + name: Donghyun Oh + photo: donghyunoh.jpg + position: MS/PhD Student + + sungbinshin: + name: Sungbin Shin + photo: sungbinshin.jpg + position: MS/PhD Student + url: https://ssbin4.github.io + + dongyeoplee: + name: Dongyeop Lee + photo: dongyeoplee.jpg + position: PhD Student + url: https://dongyeoplee2.github.io + + jihunkim: + name: Jihun Kim + photo: jihunkim.jpg + position: MS/PhD Student + + kwanheelee: + name: Kwanhee Lee + photo: kwanheelee.jpg + position: MS Student + url: https://kwanhee-lee.github.io + + hyunjijung: + name: Hyunji Jung + photo: hyunjijung.jpg + position: MS Student + url: https://www.linkedin.com/in/hyunji-jung-1121b5276/?locale=en + + minkyoungsong: + name: Minkyoung Song + photo: minkyoungsong.jpg + position: MS Student + url: https://smk2295.github.io/ + alt: Minkyung Song + + hyeondojang: + name: Hyeondo Jang + photo: hyeondojang.jpg + position: MS Student + url: https://hyeondo-jang.github.io/ + + jiyunpark: + name: Jiyun Park + photo: jiyunpark.jpg + position: MS Student + + doyoonkim: + name: Doyoon Kim + photo: doyoonkim.jpg + position: MS Student + url: https://cosmos1030.github.io/ + + yuchankim: + name: Yuchan Kim + photo: yuchankim.jpg + position: MS Student + +alumni: + + dahunshin: + name: Dahun Shin + note: (MS 2025 → NHN) + + seonghwanpark: + name: Seonghwan Park + url: https://shpark97.github.io/ + note: (MS 2025 → KETI) + + dongyeoplee: + name: Dongyeop Lee + url: https://dongyeoplee2.github.io + note: (MS 2025, continuing as PhD) + + jaehyeonjeong: + name: Jaehyeon Jeong + note: (MS 2026 → ETRI) diff --git a/group_profile/damiso.jpg b/data/members/photos/damiso.jpg similarity index 100% rename from group_profile/damiso.jpg rename to data/members/photos/damiso.jpg diff --git a/group_profile/donghyunoh.jpg b/data/members/photos/donghyunoh.jpg similarity index 100% rename from group_profile/donghyunoh.jpg rename to data/members/photos/donghyunoh.jpg diff --git a/group_profile/dongyeoplee.jpg b/data/members/photos/dongyeoplee.jpg similarity index 100% rename from group_profile/dongyeoplee.jpg rename to data/members/photos/dongyeoplee.jpg diff --git a/group_profile/doyoonkim.jpg b/data/members/photos/doyoonkim.jpg similarity index 100% rename from group_profile/doyoonkim.jpg rename to data/members/photos/doyoonkim.jpg diff --git a/group_profile/hyeondojang.jpg b/data/members/photos/hyeondojang.jpg similarity index 100% rename from group_profile/hyeondojang.jpg rename to data/members/photos/hyeondojang.jpg diff --git a/group_profile/hyunjijung.jpg b/data/members/photos/hyunjijung.jpg similarity index 100% rename from group_profile/hyunjijung.jpg rename to data/members/photos/hyunjijung.jpg diff --git a/group_profile/jaehyeonjeong.jpg b/data/members/photos/jaehyeonjeong.jpg similarity index 100% rename from group_profile/jaehyeonjeong.jpg rename to data/members/photos/jaehyeonjeong.jpg diff --git a/group_profile/jihunkim.jpg b/data/members/photos/jihunkim.jpg similarity index 100% rename from group_profile/jihunkim.jpg rename to data/members/photos/jihunkim.jpg diff --git a/group_profile/jinseokchung.jpg b/data/members/photos/jinseokchung.jpg similarity index 100% rename from group_profile/jinseokchung.jpg rename to data/members/photos/jinseokchung.jpg diff --git a/group_profile/jiyunpark.jpg b/data/members/photos/jiyunpark.jpg similarity index 100% rename from group_profile/jiyunpark.jpg rename to data/members/photos/jiyunpark.jpg diff --git a/group_profile/kwanheelee.jpg b/data/members/photos/kwanheelee.jpg similarity index 100% rename from group_profile/kwanheelee.jpg rename to data/members/photos/kwanheelee.jpg diff --git a/group_profile/minkyoungsong.jpg b/data/members/photos/minkyoungsong.jpg similarity index 100% rename from group_profile/minkyoungsong.jpg rename to data/members/photos/minkyoungsong.jpg diff --git a/group_profile/namhoonlee.jpg b/data/members/photos/namhoonlee.jpg similarity index 100% rename from group_profile/namhoonlee.jpg rename to data/members/photos/namhoonlee.jpg diff --git a/group_profile/sungbinshin.jpg b/data/members/photos/sungbinshin.jpg similarity index 100% rename from group_profile/sungbinshin.jpg rename to data/members/photos/sungbinshin.jpg diff --git a/group_profile/yuchankim.jpg b/data/members/photos/yuchankim.jpg similarity index 100% rename from group_profile/yuchankim.jpg rename to data/members/photos/yuchankim.jpg diff --git a/data/news/2023.yaml b/data/news/2023.yaml new file mode 100644 index 0000000..9214944 --- /dev/null +++ b/data/news/2023.yaml @@ -0,0 +1,35 @@ +# News from 2023, newest first. How to add an item: README.md. +# +# Every field of an item (the item's key is its idx): +# +# 39: # idx, required. Unique across all years; higher = newer (sets the order) +# date: "2023-10" # required. "YYYY-MM" or "YYYY-MM-DD", within this file's year +# paper: "@lee26fresco" # optional. papers.bib key(s) the item is about but does not link: +# # "@lee26fresco" or ["@lee26fresco", "@jang26hidden"] +# text: >- # required. One paragraph of Markdown, starting with its emoji: +# 🎓 Our [paper](@shin23closer) ... @hyunjijung ... +# # [text](@paperkey) links a paper, [text](https://...) any page, +# # @tag names a lab member (data/members/members.yaml) + +4: + date: "2023-11" + text: >- + 🏆 Our [paper](@shin25critical) on overparameterization on SAM received the Best Paper + Award at JKAIA 2023. + +3: + date: "2023-04" + text: >- + 🏆 @sungbinshin has been selected for the Kwanjeong Educational Foundation Graduate + Scholarship. + +2: + date: "2023-04" + text: >- + 🎓 Our [paper](@shin23closer) on concept bottleneck models has been accepted to ICML + 2023. + +1: + date: "2023-02" + text: >- + 🧑‍💻 @sungbinshin joined Google as a Student Researcher. diff --git a/data/news/2024.yaml b/data/news/2024.yaml new file mode 100644 index 0000000..bec2f43 --- /dev/null +++ b/data/news/2024.yaml @@ -0,0 +1,22 @@ +# News from 2024, newest first. How to add an item: README.md. +# +# Every field of an item (the item's key is its idx): +# +# 39: # idx, required. Unique across all years; higher = newer (sets the order) +# date: "2024-10" # required. "YYYY-MM" or "YYYY-MM-DD", within this file's year +# paper: "@lee26fresco" # optional. papers.bib key(s) the item is about but does not link: +# # "@lee26fresco" or ["@lee26fresco", "@jang26hidden"] +# text: >- # required. One paragraph of Markdown, starting with its emoji: +# 🎓 Our [paper](@shin23closer) ... @hyunjijung ... +# # [text](@paperkey) links a paper, [text](https://...) any page, +# # @tag names a lab member (data/members/members.yaml) + +6: + date: "2024-09" + text: >- + 🧑‍💻 @dongyeoplee joined Google as a Student Researcher. + +5: + date: "2024-09" + text: >- + 🎓 Our [paper](@shin24rethinking) on LLM pruning has been accepted to EMNLP 2024. diff --git a/data/news/2025.yaml b/data/news/2025.yaml new file mode 100644 index 0000000..6d8302d --- /dev/null +++ b/data/news/2025.yaml @@ -0,0 +1,73 @@ +# News from 2025, newest first. How to add an item: README.md. +# +# Every field of an item (the item's key is its idx): +# +# 39: # idx, required. Unique across all years; higher = newer (sets the order) +# date: "2025-10" # required. "YYYY-MM" or "YYYY-MM-DD", within this file's year +# paper: "@lee26fresco" # optional. papers.bib key(s) the item is about but does not link: +# # "@lee26fresco" or ["@lee26fresco", "@jang26hidden"] +# text: >- # required. One paragraph of Markdown, starting with its emoji: +# 🎓 Our [paper](@shin23closer) ... @hyunjijung ... +# # [text](@paperkey) links a paper, [text](https://...) any page, +# # @tag names a lab member (data/members/members.yaml) + +16: + date: "2025-11" + text: >- + 🏆 @dongyeoplee received the [Qualcomm Innovation Fellowship Korea + 2025](https://www.qualcomm.com/research/university-relations/innovation-fellowship/2025-south-korea). + +15: + date: "2025-11" + text: >- + 🏆 Our [paper](@lee26unseen) on extreme LLM sparsity (ELSA) received the Best Paper + Award at JKAIA 2025. + +14: + date: "2025-11" + text: >- + 🏆 @hyunjijung received the NRF Fellowship. + +13: + date: "2025-09" + text: >- + 🎓 We have 2 papers ([MemEIC](@seong25memeic) and [An Analysis of Concept Bottleneck + Models](@park25analysis)) accepted to NeurIPS 2025. + +12: + date: "2025-08" + text: >- + 🤝 Our lab has been selected for the 2025 High-Performance Computing Support Program, + providing cloud access to four NVIDIA H200 GPUs for approximately six months. + +11: + date: "2025-05" + text: >- + 🤝 Our lab has been selected for the [2025 Global Basic Research Laboratory + program](https://www.msit.go.kr/bbs/view.do?bbsSeqNo=100&mId=311&mPid=121&nttSeqNo=3179520&sCode=user) + (NRF), with up to *KRW 1.5B* in funding over three years. + +10: + date: "2025-05" + text: >- + 🎓 Our [paper](@shin25critical) on overparameterization on SAM has been accepted to + UAI 2025. + +9: + date: "2025-05" + text: >- + 🎓 Our works on second-order optimization ([SASSHA](@shin25sassha)) and pruning + ([SAFE](@lee25safe)) have been accepted to ICML 2025. SAFE is also selected as a + spotlight poster. + +8: + date: "2025-05" + text: >- + 🎓 Our [paper](@park25zip) on zeroth-order prompt tuning for black-box VLM (ZIP) has + been accepted to ICLR 2025. + +7: + date: "2025-04" + text: >- + 🏆 @hyeondojang has been selected for the Kwanjeong Educational Foundation Graduate + Scholarship. diff --git a/data/news/2026.yaml b/data/news/2026.yaml new file mode 100644 index 0000000..beaf5d8 --- /dev/null +++ b/data/news/2026.yaml @@ -0,0 +1,170 @@ +# News from 2026, newest first. How to add an item: README.md. +# +# Every field of an item (the item's key is its idx): +# +# 39: # idx, required. Unique across all years; higher = newer (sets the order) +# date: "2026-10" # required. "YYYY-MM" or "YYYY-MM-DD", within this file's year +# paper: "@lee26fresco" # optional. papers.bib key(s) the item is about but does not link: +# # "@lee26fresco" or ["@lee26fresco", "@jang26hidden"] +# text: >- # required. One paragraph of Markdown, starting with its emoji: +# 🎓 Our [paper](@shin23closer) ... @hyunjijung ... +# # [text](@paperkey) links a paper, [text](https://...) any page, +# # @tag names a lab member (data/members/members.yaml) + +38: + date: "2026-10" + text: >- + 🎓 Our [preprint](@kim26seedflood) on decentralized LLM training, SeedFlood, has + received a major update, with substantial improvements to its overall presentation, + analysis, and experimental evaluation. + +37: + date: "2026-10" + text: >- + 🧑‍💻 @minkyoungsong has started a research internship at [Nota + AI](https://www.nota.ai/). + +36: + date: "2026-10" + text: >- + 🎓 Our [paper](@lee26hardware) on hardware-native sparse-quantization for + trillion-scale MoEs is out on arXiv. In this work, we introduce MoESQ, which jointly + learns semi-structured sparsity and low-precision quantization for MoE experts and + executes them on Sparse Tensor Cores with a custom grouped sparse GEMM kernel. The + [code](https://github.com/IST-DASLab/MoESQ) and + [models](https://huggingface.co/collections/ISTA-DASLab/moesq) are publicly + available. + +35: + date: "2026-09" + text: >- + 🎓 Our [paper](@kim26seedflood) on decentralized training has been accepted to NeurIPS + 2026 Workshop on Collaborative, Open, and Decentralized Training of Foundation + Models, selected as an oral presentation. In this work, we introduce a novel + decentralized LLM fine-tuning framework that achieves extreme communiation + efficiency and global consensus. + +34: + date: "2026-09" + paper: "@lee26fresco" + text: >- + 🎓 Our paper on asynchronous pipeline parallelism has been accepted to NeurIPS 2026. + In this work, we introduce FRESCO, a new principle for consistency control in + asynchronous pipelines, which consistently outperforms standard baselines while + being extremely efficient and theoretically sound. + +33: + date: "2026-09" + text: >- + 🎓 @namhoonlee is serving as an area chair for ICLR 2027. + +32: + date: "2026-08" + text: >- + 🏆 @hyunjijung, @hyeondojang, and @jinseokchung have been selected for the NRF + Research Grant for Master’s and PhD Students. + +31: + date: "2026-08" + text: >- + 🏛 We are co-organizing the [LeT Workshop](https://let-workshop.github.io/2026/) on + October 7~8 at POSTECH. The workshop aims to bring together researchers interested + in machine learning theory. + +30: + date: "2026-08" + text: >- + 🎓 Our [paper](@jang26hidden) on extreme LLM sparsity has been accepted to EMNLP 2026. + In this work, we show that carefully handling the elementary components of pruning + can already substantially outperform existing pruning methods, even at extreme + sparsity. + +29: + date: "2026-08" + paper: "@jeong26advancing" + text: >- + 🎓 Our paper on semiconductor anomaly detection has been accepted to BMVC 2026. In + this work, we introduce a semiconductor defect dataset (OASIS) along with + vision-guided instruction tuning method. + +28: + date: "2026-07" + text: >- + 🤝 Our lab has been selected for the 2026 NRF Next-Generation AI for Mathematics + program. + +27: + date: "2026-07" + text: >- + 🏆 @kwanheelee received the IITP President’s Award in the Graduate Student + Outstanding Research Achievement Competition at [AI Connect Korea + 2026](https://www.aiconnectkorea.kr/). + +26: + date: "2026-07" + paper: "@jang26hidden" + text: >- + 🏆 Our new paper on extreme LLM sparsity has received the Best Paper Award at CKAIA + 2026. The preprint will be available on arXiv soon. + +25: + date: "2026-07" + text: >- + 🏆 @sungbinshin has been selected for the 2026 PhD Excellence Scholarship in Science + and Engineering. + +24: + date: "2026-07" + text: >- + 🤝 Our lab joined the [National AI Research Lab + (NAIRL)](https://nairl.kr/academic-ko/) academic consortium on July 1, 2026. + +23: + date: "2026-06" + text: >- + 🎓 Our [paper](@park26exploiting) on flatness-based out-of-distribution detection has + been accepted to ECCV 2026. In this work, we propose FOLD, a lightweight detector + that leverages feature-Hessian curvature and partial feature normalization to + improve ID-OOD separability. + +22: + date: "2026-05" + text: >- + 🏆 Our [paper](@chung26quantifying) on uncertainty quantification in ICL, accepted to + ACL 2026, has been selected as an Oral presentation. In this work, we introduce + self-function vectors to directly decompose uncertainty, and propose a novel + framework to evaluate these disentangled sources. + +21: + date: "2026-05" + text: >- + 🎓 Our [paper](@jung26mitigating) on asynchronous pipeline parallelism has been + accepted to ICML 2026. In this work, we propose a basis rotation approach to + effectively address the associated gradient staleness problem. + +20: + date: "2026-04" + text: >- + 🤝 Our lab has been selected for the IITP Efficient AI National R&D Program + (2026–2029), a national research program with *KRW 7.5B* in total funding, together + with leading partners including [SqueezeBits](https://squeezebits.com/). + +19: + date: "2026-02" + text: >- + 🤝 Our lab has been selected for the "Early-Career Researcher Infrastructure Support + Program" (NRF) — one of the most competitive grants in Korea. With *500M KRW* in + funding, we will build a high-performance compute infrastructure to power our + research on extreme compression of hyperscale AI foundation models. + +18: + date: "2026-02" + text: >- + ✈️ @kwanheelee started as a visiting student researcher in the [Dan Alistarh + Group](https://daslab.pages.ista.ac.at/) at [ISTA](https://ist.ac.at/en/home/). + +17: + date: "2026-01" + text: >- + 🎓 Our [paper](@lee26unseen) on extreme LLM sparsity (ELSA) has been accepted to ICLR + 2026. diff --git a/data/news/README.md b/data/news/README.md new file mode 100644 index 0000000..57ab956 --- /dev/null +++ b/data/news/README.md @@ -0,0 +1,99 @@ +# News + +News items are kept in one YAML file per year: `2026.yaml`, `2025.yaml`, … The +News page lists them newest first, and the home page's **Recent News** shows the +newest few, both generated from these files. Nothing is copied by hand. + +## Add a news item + +Add it at the top of its year's file (create `2027.yaml` for a new year), with a +blank line between items. Each item starts with its idx: + +```yaml +39: + date: "2026-10" + text: >- + 🏆 @kwanheelee received the IITP President's Award at + [AI Connect Korea 2026](https://www.aiconnectkorea.kr/). +``` + +| Field | Required | Rule | +|---|---|---| +| `39:` (the key) | yes | The item's idx: the highest idx in any year + 1. Sets the order: higher is newer. Never reuse one. | +| `date` | yes | `"YYYY-MM"`, or `"YYYY-MM-DD"` if you know the day, in quotes. Shown as `Oct 2026`. Must be in the file's year. | +| `text` | yes | One paragraph of Markdown, after `>-` on the next lines, indented. Line breaks inside it are just spaces. | +| `paper` | no | `"@key"` (in quotes) of papers the item is about but does not link (see below). | + +Start `text` with the emoji for the item's kind: + +| Emoji | Kind | +|---|---| +| 🎓 | paper accepted / released | +| 🏆 | award, fellowship, scholarship | +| 🤝 | grant or program the lab was selected for | +| 🧑‍💻 | internship, joining a company | +| ✈️ | visit, research stay | +| 🏛 | workshop or event we organize | + +Links use Markdown: `[text](https://...)`, or `[text](@paperkey)` for a paper. +External links open in a new tab automatically. Use `*italic*` and `**bold**` if +needed. The top of each year file lists every field as a template. + +Then check it and preview it (see the repository README): + +```bash +python3 tools/build.py --check +python3 tools/build.py --serve # http://localhost:8000 +``` + +## People and papers: `@tags` and paper keys + +Refer to lab members by their **tag** from `data/members/members.yaml`. The page +shows their first name (or their `short` name), with their face under the date: + +```yaml + text: >- + 🏆 @hyunjijung, @hyeondojang, and @jinseokchung have been selected for the NRF Research Grant. +``` + +→ "Hyunji, Hyeondo, and Jinseok have been selected for …" + +For news about a paper, refer to it by its key in `data/publications/papers.bib`. +Its in-lab authors get faces too. To link to it, use `@key` as the link target; +the build fills in the paper's `paper` URL from papers.bib: + +```yaml + text: >- + 🎓 Our [paper](@shin23closer) on concept bottleneck models has been accepted to ICML 2023. +``` + +For a paper mentioned without a link (no `paper` URL yet), name it in `paper`: + +```yaml +34: + date: "2026-09" + paper: "@lee26fresco" # or a list: ["@lee26fresco", "@jang26hidden"] + text: >- + 🎓 Our paper on asynchronous pipeline parallelism has been accepted to NeurIPS 2026. +``` + +Faces appear only from these: the authors of the papers (`paper` first, then +linked ones in text order), then people `@tagged` in the text, and only for +current members. Alumni tags show the name +without a face. Nothing is guessed from plain names or links. An unknown tag or +paper key stops the build with an error. + +## Edit or remove an item + +Edit it in place. To remove an item, delete its entry. Gaps in the numbering are +fine. To reorder two items, swap their numbers. + +## Presentation settings (`config.toml`) + +| Setting | Meaning | +|---|---| +| `date_format` | How the date is written (`"%b %Y"` → `Oct 2026`) | +| `home.count` | Items in Recent News on the home page | +| `page.since` | Oldest month listed on the News page. Older items stay in their year files as the archive but are not shown | +| `page.initial` / `page.step` | Items shown when the News page opens / added per **Show more** click | +| `faces.max` | Faces under a date before the rest collapse into "+N" | diff --git a/data/news/config.toml b/data/news/config.toml new file mode 100644 index 0000000..754d7f2 --- /dev/null +++ b/data/news/config.toml @@ -0,0 +1,18 @@ +# News settings. Details: README.md. + +date_format = "%b %Y" # date under each item → "Oct 2026" + + +[home] +count = 5 # items in Recent News on the home page + + +[page] +since = "2026-01" # oldest month on the News page; older items are kept but hidden + +initial = 10 # items shown at first +step = 5 # items added per "Show more" + + +[faces] +max = 3 # faces under a date before "+N" diff --git a/data/publications/README.md b/data/publications/README.md new file mode 100644 index 0000000..795c1fa --- /dev/null +++ b/data/publications/README.md @@ -0,0 +1,52 @@ +# Publications + +All papers are in one BibTeX file, `papers.bib`. The Publications page groups +them under **Preprint**, then each year (newest first), then **Previous**. Within a +group, papers appear in the order they are written in the file. + +## Add a paper + +Add an entry where it should appear: + +```bibtex +@inproceedings{jang26hidden, + title = {Hidden in Plain Sight: The Overlooked Significance of Canonical Elements for Extreme LLM Sparsity}, + author = {@hyeondojang and @kwanheelee and @dongyeoplee and @namhoonlee}, + venue = {EMNLP 2026}, + year = {2026}, + award = {CKAIA 2026 Best Paper Award}, + paper = {https://arxiv.org/abs/2609.06557}, +} +``` + +The key (`jang26hidden`) is first author's last name + 2-digit year + first title +word. News items refer to the paper by it as `@jang26hidden`, so keep it once +chosen. + +| Field | Kind | Meaning | +|---|---|---| +| `title` | text | Required. Write `\&` for &. | +| `author` | text | Required. Names joined by `and`. Lab members and alumni by **tag**: `@hyunjijung`; current members get their face. Others by name: `Dan Alistarh`. Mark equal contribution with `*`: `@hyunjijung*`. | +| `venue` | text | Required. As printed: `NeurIPS 2026`, `ICML 2026 Workshop on ...`, `arXiv 2026`. | +| `year` | text | Required. Decides the year heading. | +| `oral` / `spotlight` | true/false | Adds a highlighted `(Oral)` / `(Spotlight)` after the venue. | +| `also` | text | Another venue of the same paper: `ICLR 2023 Workshop on ...`. | +| `award` | text | Shown highlighted at the end: `CKAIA 2026 Best Paper Award`. | +| `is_preprint` | true/false | Listed under **Preprint**, above the years. | +| `is_previous` | true/false | Listed under **Previous**, below the years (work before the lab). | +| `spaced_links` | true/false | Space between this paper's links (see note below). | +| *anything else* | URL | A link, labelled with the field name, in the order written. In use: `paper`, `code`, `code-torch`, `code-jax`, `model`, `OpenReview`. A new name (e.g. `slides`) just works. | + +Fields left out are false or empty. When a preprint is accepted, remove +`is_preprint` and update `venue` and `year`. + +## Presentation settings (`config.toml`) + +| Setting | Meaning | +|---|---| +| `groups.preprint` / `groups.previous` | The two special headings | +| `links.separator` | Text between a paper's links | + +> `spaced_links = {true}` reproduces the page as it was: until now, some papers +> had a space between `paper` and `code` and most did not. Delete those lines to +> make every paper the same. diff --git a/data/publications/config.toml b/data/publications/config.toml new file mode 100644 index 0000000..14e3e66 --- /dev/null +++ b/data/publications/config.toml @@ -0,0 +1,9 @@ +# Publications settings. Details: README.md. + +[groups] +preprint = "Preprint" # heading for is_preprint papers (first) +previous = "Previous" # heading for is_previous papers (last) + + +[links] +separator = "" # between a paper's links; spaced_links = {true} uses a space diff --git a/data/publications/papers.bib b/data/publications/papers.bib new file mode 100644 index 0000000..49c71ef --- /dev/null +++ b/data/publications/papers.bib @@ -0,0 +1,405 @@ +% Lab publications, shown on the Publications page. How to add one: README.md. +% +% Headings: is_preprint papers first, then one per `year` (newest first), then +% is_previous papers. Within a heading, papers keep the order written here; the +% "% =====" lines below are only for reading. +% +% Every field of an entry: +% +% @inproceedings{jang26hidden, key: first author's last name + yy + first title word +% title = {...}, required. \& for & +% author = {@hyeondojang and @kwanheelee and Dan Alistarh}, +% required. Lab people as @tag (members.yaml), others by +% name; * after a name marks equal contribution +% venue = {EMNLP 2026}, required. As printed +% year = {2026}, required. Sets the year heading +% oral = {true}, optional. Adds "(Oral)" after the venue +% spotlight = {true}, optional. Adds "(Spotlight)" after the venue +% also = {ICLR 2023 Workshop on ...}, optional. Another venue of the same paper +% award = {CKAIA 2026 Best Paper Award}, optional. Highlighted at the end +% is_preprint = {true}, optional. Under "Preprint" +% is_previous = {true}, optional. Under "Previous" (work before the lab) +% spaced_links = {true}, optional. Space between this paper's links +% +% Links: any other field is a link, shown with the field name as its label, in the +% order written. News links [text](@key) go to `paper`. Labels in use: +% paper = {https://...}, the paper itself (arXiv, OpenReview, journal, ...) +% code = {https://...}, code repository +% code-torch = {https://...}, code, PyTorch version (lee25safe) +% code-jax = {https://...}, code, JAX version (lee25safe) +% model = {https://...}, released models (lee26hardware) +% OpenReview = {https://...}, OpenReview page, when `paper` is elsewhere (nam23almost) +% } + +% ===== Preprint ===== + +@inproceedings{lee26hardware, + title = {Hardware-Native Joint Sparse-Quantization for Trillion-Scale Mixture-of-Experts}, + author = {@kwanheelee and @namhoonlee and Dan Alistarh}, + venue = {arXiv 2026}, + year = {2026}, + is_preprint = {true}, + paper = {https://arxiv.org/abs/2610.02241}, + code = {https://github.com/IST-DASLab/MoESQ}, + model = {https://huggingface.co/collections/ISTA-DASLab/moesq}, + spaced_links = {true}, +} + + +% ===== 2026 ===== + +@inproceedings{park26comparing, + title = {Comparing Synchronous and Asynchronous Pipeline Parallelism Across Training Configurations}, + author = {Junkyu Park and @dongyeoplee and @namhoonlee}, + venue = {NeurIPS 2026 Workshop on Collaborative, Open, and Decentralized Training of Foundation Models}, + year = {2026}, +} + +@inproceedings{kim26seedflood, + title = {SeedFlood: A Step Toward Scalable Decentralized Fine-Tuning of LLMs}, + author = {@jihunkim and @dongyeoplee and @namhoonlee}, + venue = {NeurIPS 2026 Workshop on Collaborative, Open, and Decentralized Training of Foundation Models}, + year = {2026}, + oral = {true}, + paper = {https://arxiv.org/abs/2602.18181}, +} + +@inproceedings{lee26fresco, + title = {FRESCO: A Novel Consistency Control for Asynchronous Pipeline Parallel Training}, + author = {@dongyeoplee and @namhoonlee}, + venue = {NeurIPS 2026}, + year = {2026}, +} + +@inproceedings{jang26hidden, + title = {Hidden in Plain Sight: The Overlooked Significance of Canonical Elements for Extreme LLM Sparsity}, + author = {@hyeondojang and @kwanheelee and @dongyeoplee and @namhoonlee}, + venue = {EMNLP 2026}, + year = {2026}, + award = {CKAIA 2026 Best Paper Award}, + paper = {https://arxiv.org/abs/2609.06557}, +} + +@inproceedings{jeong26advancing, + title = {Advancing Semiconductor Inspection: A New Dataset and Approach for Robust Anomaly Detection with Large Vision Language Models}, + author = {@jaehyeonjeong* and @hyeondojang* and @donghyunoh and Seongeun Kim and @namhoonlee}, + venue = {BMVC 2026}, + year = {2026}, +} + +@inproceedings{park26exploiting, + title = {Exploiting Local Flatness for Efficient Out-of-Distribution Detection}, + author = {@seonghwanpark and @hyunjijung and @dongyeoplee and @namhoonlee}, + venue = {ECCV 2026}, + year = {2026}, + paper = {https://arxiv.org/abs/2606.29952v1}, + code = {https://github.com/shpark97/Fold}, + spaced_links = {true}, +} + +@inproceedings{kim26policy, + title = {On-Policy Adaptation Mitigates Hyperparameter-Sensitive Forgetting in Vision-Language Models}, + author = {@doyoonkim* and Ziyang Wu* and Jingfeng Yang and Yi Ma}, + venue = {ICML 2026 Workshop on Continual Adaptation at Scale: Towards Sustainable AI}, + year = {2026}, + paper = {https://openreview.net/forum?id=hSugVMRCLK}, +} + +@inproceedings{jung26mitigating, + title = {Mitigating Staleness in Asynchronous Pipeline Parallelism via Basis Rotation}, + author = {@hyunjijung* and @sungbinshin* and @namhoonlee}, + venue = {ICML 2026}, + year = {2026}, + paper = {https://www.arxiv.org/abs/2602.03515v2}, + code = {https://github.com/LOG-postech/basis-rotation}, +} + +@inproceedings{chung26quantifying, + title = {Quantifying Aleatoric Uncertainty of In-Context Learning for Robust Measure of LLM Prediction Confidence}, + author = {@jinseokchung and @minkyoungsong* and @hyunjijung* and @namhoonlee}, + venue = {ACL 2026}, + year = {2026}, + oral = {true}, + award = {KCC 2026 Workshop Outstanding Paper Award}, + paper = {https://arxiv.org/abs/2606.19353}, + code = {https://github.com/LOG-postech/self-fv-icl}, + spaced_links = {true}, +} + +@inproceedings{lee26dynamic, + title = {Dynamic Network Compression via Probabilistic Channel Pruning}, + author = {@kwanheelee and Hyang Won Lee}, + venue = {Neural Networks 2026}, + year = {2026}, + paper = {https://www.sciencedirect.com/science/article/pii/S0893608025009608}, +} + +@inproceedings{lee26unseen, + title = {The Unseen Frontier: Pushing the Limits of LLM Sparsity with Surrogate-Free ADMM}, + author = {@kwanheelee and @hyeondojang and @dongyeoplee and Dan Alistarh and @namhoonlee}, + venue = {ICLR 2026}, + year = {2026}, + award = {JKAIA 2025 Best Paper Award}, + paper = {https://arxiv.org/abs/2510.01650}, + code = {https://github.com/LOG-postech/elsa}, +} + + +% ===== 2025 ===== + +@inproceedings{seong25memeic, + title = {MemEIC: A Step Toward Continual and Compositional Knowledge Editing}, + author = {Jin Seong* and @jiyunpark* and Wencke Liermann and Hongseok Choi and Yoonji Nam and Hyun Kim and Soojong Lim and @namhoonlee}, + venue = {NeurIPS 2025}, + year = {2025}, + paper = {https://arxiv.org/abs/2510.25798}, + code = {https://github.com/MemEIC/MemEIC}, + spaced_links = {true}, +} + +@inproceedings{park25analysis, + title = {An Analysis of Concept Bottleneck Models: Measuring, Understanding, and Mitigating the Impact of Noisy Annotations}, + author = {@seonghwanpark and Jueun Mun and @donghyunoh and @namhoonlee}, + venue = {NeurIPS 2025}, + year = {2025}, + paper = {https://arxiv.org/abs/2505.16705}, + code = {https://github.com/LOG-postech/CBM-Noise}, +} + +@inproceedings{shin25critical, + title = {Critical Influence of Overparameterization on Sharpness-aware Minimization}, + author = {@sungbinshin* and @dongyeoplee* and Maksym Andriushchenko and @namhoonlee}, + venue = {UAI 2025}, + year = {2025}, + also = {ICML 2023 Workshop on High-dimensional Learning Dynamics}, + award = {JKAIA 2023 Best Paper Award}, + paper = {https://arxiv.org/abs/2311.17539}, + code = {https://github.com/LOG-postech/SAM-overparam}, +} + +@inproceedings{lee25safe, + title = {SAFE: Finding Sparse and Flat Minima to Improve Pruning}, + author = {@dongyeoplee and @kwanheelee and @jinseokchung and @namhoonlee}, + venue = {ICML 2025}, + year = {2025}, + spotlight = {true}, + paper = {https://arxiv.org/abs/2506.06866}, + code-torch = {https://github.com/log-postech/safe-torch}, + code-jax = {https://github.com/LOG-postech/safe-jax}, +} + +@inproceedings{shin25sassha, + title = {SASSHA: Sharpness-aware Adaptive Second-order Optimization with Stable Hessian Approximation}, + author = {@dahunshin* and @dongyeoplee* and @jinseokchung and @namhoonlee}, + venue = {ICML 2025}, + year = {2025}, + paper = {https://arxiv.org/abs/2502.18153}, + code = {https://github.com/LOG-postech/Sassha}, +} + +@inproceedings{park25zip, + title = {ZIP: An Efficient Zeroth-order Prompt Tuning for Black-box Vision-Language Models}, + author = {@seonghwanpark and @jaehyeonjeong and Yongjun Kim and Jaeho Lee and @namhoonlee}, + venue = {ICLR 2025}, + year = {2025}, + paper = {https://arxiv.org/abs/2504.06838}, + code = {https://github.com/LOG-postech/ZIP}, +} + +@inproceedings{jung25convergence, + title = {Convergence and Implicit Bias of Gradient Descent on Continual Linear Classification}, + author = {@hyunjijung* and Hanseul Cho* and Chulhee Yun}, + venue = {ICLR 2025}, + year = {2025}, + paper = {https://arxiv.org/abs/2504.12712}, +} + + +% ===== 2024 ===== + +@inproceedings{shin24rethinking, + title = {Rethinking Pruning Large Language Models: Benefits and Pitfalls of Reconstruction Error Minimization}, + author = {@sungbinshin and Wonpyo Park and Jaeho Lee and @namhoonlee}, + venue = {EMNLP 2024}, + year = {2024}, + paper = {https://arxiv.org/abs/2406.15524}, + code = {https://github.com/LOG-postech/rethinking-LLM-pruning}, +} + +@inproceedings{son24role, + title = {The Role of Masking for Efficient Supervised Knowledge Distillation of Vision Transformers}, + author = {Seungwoo Son and Jegwang Ryu and @namhoonlee and Jaeho Lee}, + venue = {ECCV 2024}, + year = {2024}, + also = {ICLR 2023 Workshop}, + paper = {https://arxiv.org/abs/2302.10494}, +} + +@inproceedings{lee24jaxpruner, + title = {JaxPruner: A Concise Library for Sparsity Research}, + author = {Joo Hyung Lee and Wonpyo Park and Nicole Mitchell and Jonathan Pilault and Johan Obando-Ceron and Han-Byul Kim and @namhoonlee and Elias Frantar and Yun Long and Amir Yazdanbakhsh and Shivani Agrawal and Suvinay Subramanian and Xin Wang and Sheng-Chun Kao and Xingyao Zhang and Trevor Gale and Aart Bik and Woohyun Han and Milen Ferez and Zhonglin Han and Hong-Seok Kim and Yann Dauphin and Gintare Karolina Dziugaite and Pablo Samuel Castro and Utku Evci}, + venue = {CPAL 2024}, + year = {2024}, + oral = {true}, + also = {ICLR 2023 Workshop on Sparsity in Neural Networks}, + paper = {https://arxiv.org/abs/2304.14082}, +} + + +% ===== 2023 ===== + +@inproceedings{oh23pruning, + title = {Pruning Neural Networks with Velocity-Constrained Optimization}, + author = {@donghyunoh* and @jinseokchung* and @namhoonlee}, + venue = {NeurIPS 2023 Workshop on Optimization for Machine Learning}, + year = {2023}, + paper = {https://opt-ml.org/papers.html}, +} + +@inproceedings{shin23closer, + title = {A Closer Look at the Intervention Procedure of Concept Bottleneck Models}, + author = {@sungbinshin and Yohan Jo and Sungsoo Ahn and @namhoonlee}, + venue = {ICML 2023}, + year = {2023}, + also = {NeurIPS 2022 Workshop on Trustworthy and Socially Responsible ML}, + paper = {https://arxiv.org/abs/2302.14260}, + code = {https://github.com/ssbin4/Closer-Intervention-CBM}, +} + +@inproceedings{chung23effectiveness, + title = {On the Effectiveness of Sharpness-Aware Minimization with Large Mini-batches}, + author = {@jinseokchung and @seonghwanpark and Jaeho Lee and @namhoonlee}, + venue = {ICML 2023 Workshop on High-dimensional Learning Dynamics}, + year = {2023}, + paper = {https://sites.google.com/view/hidimlearning/papers}, +} + +@inproceedings{park23fedfwd, + title = {FedFwd: Federated Learning without Backpropagation}, + author = {@seonghwanpark and @dahunshin and @jinseokchung and @namhoonlee}, + venue = {ICML 2023 Workshop on Federated Learning and Analytics in Practice}, + year = {2023}, + paper = {https://fl-icml2023.github.io/}, +} + +@inproceedings{bae23semi, + title = {Semi-Supervised Concept Bottleneck Models}, + author = {Jeeon Bae and @sungbinshin and @namhoonlee}, + venue = {ICML 2023 Workshop on Artificial Intelligence and Human-Computer Interaction}, + year = {2023}, + paper = {https://sites.google.com/view/aihci/papers}, +} + +@inproceedings{nam23almost, + title = {Almost Sure Last Iterate Convergence of Sharpness-Aware Minimization}, + author = {Kyungchun Nam and @jinseokchung and @namhoonlee}, + venue = {ICLR 2023 Tiny Papers}, + year = {2023}, + OpenReview = {https://openreview.net/forum?id=IcDTYTI0Nx}, +} + + +% ===== Previous ===== + +@inproceedings{lee21meta, + title = {Meta-Learning Sparse Implicit Neural Representations}, + author = {Jaeho Lee* and Jihoon Tack* and @namhoonlee and Jinwoo Shin}, + venue = {NeurIPS 2021}, + year = {2021}, + also = {SNN 2021}, + is_previous = {true}, + paper = {https://arxiv.org/abs/2110.14678}, +} + +@inproceedings{lee21understanding, + title = {Understanding the Effects of Data Parallelism and Sparsity on Neural Network Training}, + author = {@namhoonlee and Thalaiyasingam Ajanthan and Philip H. S. Torr and Martin Jaggi}, + venue = {ICLR 2021}, + year = {2021}, + is_previous = {true}, + paper = {https://arxiv.org/abs/2003.11316}, +} + +@inproceedings{lee21optimal, + title = {Optimal Mini-Batch Size for Stochastic Gradient Methods}, + author = {@namhoonlee and Philip H. S. Torr and Richard Hartley}, + venue = {ICLR 2021 Workshop on Science and Engineering of Deep Learning}, + year = {2021}, + is_previous = {true}, + paper = {https://sites.google.com/view/sedl-workshop}, +} + +@inproceedings{lee20signal, + title = {A Signal Propagation Perspective for Pruning Neural Networks at Initialization}, + author = {@namhoonlee and Thalaiyasingam Ajanthan and Stephen Gould and Philip H. S. Torr}, + venue = {ICLR 2020}, + year = {2020}, + spotlight = {true}, + is_previous = {true}, + paper = {https://arxiv.org/abs/1906.06307}, +} + +@inproceedings{lee20data, + title = {Data Parallelism in Training Sparse Neural Networks}, + author = {@namhoonlee and Philip H. S. Torr and Martin Jaggi}, + venue = {ICLR 2020 Workshop on Practical ML}, + year = {2020}, + is_previous = {true}, + paper = {https://arxiv.org/abs/2003.11316}, +} + +@inproceedings{lee19snip, + title = {SNIP: Single-shot Network Pruning based on Connection Sensitivity}, + author = {@namhoonlee and Thalaiyasingam Ajanthan and Philip H. S. Torr}, + venue = {ICLR 2019}, + year = {2019}, + is_previous = {true}, + paper = {https://arxiv.org/abs/1810.02340}, +} + +@inproceedings{jetley18learn, + title = {Learn to Pay Attention}, + author = {Saumya Jetley and Nicholas Lord and @namhoonlee and Philip H. S. Torr}, + venue = {ICLR 2018}, + year = {2018}, + is_previous = {true}, + paper = {https://arxiv.org/abs/1804.02391}, +} + +@inproceedings{hattori18synthesizing, + title = {Synthesizing a Scene-Specific Pedestrian Detector and Pose Estimator for Static Video Surveillance}, + author = {Hironori Hattori* and @namhoonlee* and Vishnu N. Boddeti and Fares Beainy and Kris M. Kitani and Takeo Kanade}, + venue = {IJCV 2018}, + year = {2018}, + is_previous = {true}, + paper = {https://link.springer.com/article/10.1007/s11263-018-1077-3}, +} + +@inproceedings{lee17desire, + title = {DESIRE: Distant Future Prediction in Dynamic Scenes with Interacting Agents}, + author = {@namhoonlee and Wongun Choi and Paul Vernaza and Christopher B. Choy and Philip H. S. Torr and Manmohan Chandraker}, + venue = {CVPR 2017}, + year = {2017}, + spotlight = {true}, + is_previous = {true}, + paper = {https://arxiv.org/abs/1704.04394}, +} + +@inproceedings{ma17forecasting, + title = {Forecasting Interactive Dynamics of Pedestrians with Fictitious Play}, + author = {Wei-Chiu Ma and De-An Huang and @namhoonlee and Kris M. Kitani}, + venue = {CVPR 2017}, + year = {2017}, + is_previous = {true}, + paper = {https://arxiv.org/abs/1604.01431}, +} + +@inproceedings{lee16predicting, + title = {Predicting Wide Receiver Trajectories in American Football}, + author = {@namhoonlee and Kris M. Kitani}, + venue = {WACV 2016}, + year = {2016}, + oral = {true}, + is_previous = {true}, + paper = {https://namhoonlee.github.io/doc/LK_WACV2016.pdf}, +} diff --git a/data/site.toml b/data/site.toml new file mode 100644 index 0000000..eb559df --- /dev/null +++ b/data/site.toml @@ -0,0 +1,47 @@ +# Site-wide content shared by every page: the header, navigation and footer. +# The page frame itself (layout, shared CSS) is layout/base.html. + +lab_name = "Lee Optimization Group" +copyright = "© 2026 LOG. All rights reserved." + +# Navigation, in order. `page` is the template name; its link gets the +# "active" style on that page. +[[nav]] +page = "index" +label = "Home" + +[[nav]] +page = "research" +label = "Research" + +[[nav]] +page = "publications" +label = "Publications" + +[[nav]] +page = "group" +label = "Members" + +[[nav]] +page = "hiring" +label = "Hiring" + +[[nav]] +page = "contact" +label = "Contact" + +# Footer logos, left to right. Images are in static/logo/. +[[footer_logos]] +url = "https://www.postech.ac.kr" +image = "logo/postech_logo.png" +alt = "POSTECH" + +[[footer_logos]] +url = "https://ai.postech.ac.kr" +image = "logo/gsai_logo.png" +alt = "GSAI" + +[[footer_logos]] +url = "https://cse.postech.ac.kr" +image = "logo/cs_logo.png" +alt = "CS" diff --git a/gallery.html b/gallery.html deleted file mode 100644 index e8fdff2..0000000 --- a/gallery.html +++ /dev/null @@ -1,723 +0,0 @@ - - - - - - - Gallery | Lee Optimization Group - - - - - -
- -
- Lee Optimization Group - - -
- -
-

Gallery

- - -
- -
- © 2026 LOG. All rights reserved. - -
- -
- - - - - - - diff --git a/group.html b/group.html deleted file mode 100644 index b3cc85e..0000000 --- a/group.html +++ /dev/null @@ -1,819 +0,0 @@ - - - - - - - Group | Lee Optimization Group - - - - - -
- -
- Lee Optimization Group - - -
- -
-

Members

-
    - -
  • - Namhoon Lee - Namhoon Lee - Principal Investigator -
  • - -
  • - Dami So - Dami So - Administrator -
  • - -
  • - Jinseok Chung - Jinseok Chung - MS/PhD Student -
  • - -
  • - Donghyun Oh - Donghyun Oh - MS/PhD Student -
  • - -
  • - Sungbin Shin - Sungbin Shin - MS/PhD Student -
  • - -
  • - Dongyeop Lee - Dongyeop Lee - PhD Student -
  • - -
  • - Jihun Kim - Jihun Kim - MS/PhD Student -
  • - -
  • - Kwanhee Lee - Kwanhee Lee - MS Student -
  • - -
  • - Hyunji Jung - Hyunji Jung - MS Student -
  • - -
  • - Minkyung Song - Minkyoung Song - MS Student -
  • - -
  • - Hyeondo Jang - Hyeondo Jang - MS Student -
  • - -
  • - Jiyun Park - Jiyun Park - MS Student -
  • - -
  • - Doyoon Kim - Doyoon Kim - MS Student -
  • - -
  • - Yuchan Kim - Yuchan Kim - MS Student -
  • - -
- -

Interns

-
    -
  • Minsung Kang (POSTECH)
  • -
  • Junkyu Park (POSTECH)
  • -
  • Wuvin An (POSTECH)
  • -
- -

Gallery

-

We also spend time together on various occasions. For more stories, check out here!

- - -

Alumni

-
    -
  • Dahun Shin (MS 2025 → NHN)
  • -
  • Seonghwan Park (MS 2025 → KETI)
  • -
  • Dongyeop Lee (MS 2025, continuing as PhD)
  • -
  • Jaehyeon Jeong (MS 2026 → ETRI)
  • -
-
- -
- © 2026 LOG. All rights reserved. - -
- -
- - - - - - - - - diff --git a/hiring.html b/hiring.html deleted file mode 100644 index 6b498f3..0000000 --- a/hiring.html +++ /dev/null @@ -1,452 +0,0 @@ - - - - - - - Hiring | Lee Optimization Group - - - - - -
- -
- Lee Optimization Group - - -
- -
-

Lab Documentary

-
- -
- -

Note on Hiring

-

- Thank you for your interest in joining our group. We are always on the lookout for highly motivated and talented people to work on optimization. - Please review carefully the guidance below and submit this application form if you're applying for a position. - While we encourage you to contact us with any inquiries, please be aware that due to the high volume of emails, - we may not be able to offer individual responses. - Common questions for joining our lab are answered here — we encourage you to check it out first. -

- -

Internship

-

- We highly recommend all prospective students to participate in an internship with us before formally applying to the graduate program. - While direct application is an option, pursuing an internship first is the most common route for successful applicants: -

-
    -
  • It allows us to get to know each other and lets you experience the lab firsthand.
  • -
  • We rarely hire students we haven't worked with before.
  • -
  • The POSTECH admission process is highly competitive, and a positive internship record can significantly strengthen your application.
  • -
-

- Summer and Winter internships are available. All internships start at the beginning of off-term times - (mid-June for Summer, early-January for Winter) and are expected to last at least two months (ideally longer). - Applications are accepted until one month prior to the start of each internship - (i.e., May for Summer and November for Winter). - Selected candidates will be invited for an interview shortly after the deadline. -

- -

- 🔔 - Applications for the 27 Winter internship will be received from 2 November 2026 to 29 November 2026. - -

- -

Graduate Program

-

- We take on a few new graduate students each year. Please apply directly to either the AI or CSE department, - and mention our PI in your application. Once you pass the screening phase and reach the advisor-matching stage, - please email PI (namhoon.lee@postech.ac.kr) and also submit this form. -

- -

Postdoc

-

- We're actively looking for postdocs who are interested in advising students with us in our lab. - If you have completed your PhD recently or are finishing it soon, please feel free to email PI. -

- -

All

-

- Please submit this application form. -

-
- -
- © 2026 LOG. All rights reserved. - -
- -
- - - - - - - - - diff --git a/index.html b/index.html deleted file mode 100644 index fe168ce..0000000 --- a/index.html +++ /dev/null @@ -1,754 +0,0 @@ - - - - - - - Home | Lee Optimization Group - - - - -
-
- Lee Optimization Group - - -
- -
- Lee Optimization Group members together outdoors by a lake in autumn - -

- We pursue research in machine learning and optimization. To this end, - we develop theories and algorithms using computational and - mathematical tools. Our ultimate goal is to provide robust and - provable solutions to challenging problems in artificial intelligence, - particularly those in large-scale settings. We are passionate about - translating our findings into practical applications that can benefit - society. For further details on our research directions and ongoing - projects, please refer to the - Research. -

- -

Recent News

-
- -
- Oct 2026 - 🧑‍💻 Minkyoung has started a research internship at Nota AI. -
- -
- Oct 2026 - 🎓 Our paper on hardware-native sparse-quantization for trillion-scale MoEs is out on arXiv. In this work, we introduce MoESQ, which jointly learns semi-structured sparsity and low-precision quantization for MoE experts and executes them on Sparse Tensor Cores with a custom grouped sparse GEMM kernel. The code and models are publicly available. -
- -
- Sep 2026 - 🎓 Our paper on asynchronous pipeline parallelism has been accepted to NeurIPS 2026. In this work, we introduce FRESCO, a new principle for consistency control in asynchronous pipelines, which consistently outperforms standard baselines while being extremely efficient and theoretically sound. -
- -
- Aug 2026 - 🏆 Hyunji, Hyeondo, and Jinseok have been selected for the NRF Research Grant for Master’s and PhD Students. -
- -
- Aug 2026 - 🏛 We are co-organizing the LeT Workshop on October 7~8 at POSTECH. The workshop aims to bring together researchers interested in machine learning theory. -
- - - - - - - - - -
- -

If you want to see our earlier news, check out here!

- - -
- -
- © 2026 LOG. All rights reserved. - -
-
- - - - - - - diff --git a/layout/base.html b/layout/base.html new file mode 100644 index 0000000..753003a --- /dev/null +++ b/layout/base.html @@ -0,0 +1,61 @@ + + + + + + + {% block title %}{% endblock %} | {{ site.lab_name }} + + + + +
+
+{% if page == "index" %} + {{ site.lab_name }} +{% else %} + {{ site.lab_name }} +{% endif %} + + +
+ +
+{% block main %}{% endblock %} +
+ +
+ {{ site.copyright }} + +
+ +
+ + + + +{% block scripts %}{% endblock %} + + diff --git a/layout/gallery.html b/layout/gallery.html new file mode 100644 index 0000000..07ba275 --- /dev/null +++ b/layout/gallery.html @@ -0,0 +1,148 @@ +{% extends "base.html" %} +{% block title %}Gallery{% endblock %} +{% block style_page %} + a { color: var(--color-primary); text-decoration: none; } + a:hover { text-decoration: underline; } + + h2 { + font-family: 'Inter', system-ui, sans-serif; + font-size: 1.20em; + font-weight: 700; + letter-spacing: 0.01em; + text-transform: none; + color: var(--color-heading); + margin-top: var(--section-gap); + margin-bottom: var(--heading-gap); + } + + h2:first-of-type { + margin-top: 0; + } + + p { + font-size: var(--text-body); + line-height: var(--leading-body); + color: var(--color-body); + margin-bottom: var(--para-gap); + } + + /* Gallery grid */ + .gallery-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(112px, 1fr)); + gap: 8px; + } + + .gallery-item { + position: relative; + overflow: hidden; + border: 1.5px solid var(--img-border); + border-radius: 6px; + aspect-ratio: 1; + } + + .gallery-item a { + display: block; + width: 100%; + height: 100%; + } + + .gallery-item img { + width: 100%; + height: 100%; + object-fit: cover; + display: block; + transition: opacity 0.2s; + } + + .gallery-item:hover img { opacity: 0.85; } + + .gallery-item .tooltip { + position: absolute; + bottom: 0; + left: 0; + right: 0; + background: rgba(0, 0, 0, 0.65); + color: var(--color-bg); + font-size: 0.7em; + text-align: center; + padding: 4px 6px; + opacity: 0; + transition: opacity 0.2s; + pointer-events: none; + } + + .gallery-item:hover .tooltip { opacity: 1; } +{% endblock %} +{% block style_after_footer %} + @media (max-width: 900px) { + :root { + --page-pad-top: 28px; + --page-pad-x: 18px; + --page-pad-bottom: 40px; + --header-pad-bottom: 4px; + --header-gap: 16px; + --footer-logo-h: 18px; + } + + header { + display: grid; + grid-template-columns: minmax(0, 1fr) auto; + align-items: start; + column-gap: 10px; + } + + .lab-name { + min-width: 0; + } + + .menu-toggle { + display: block; + } + + nav { + display: none; + } + } +{% endblock %} +{% block main %} +

Gallery

+ + + +{% endblock %} +{% block scripts %} + +{% endblock %} diff --git a/layout/group.html b/layout/group.html new file mode 100644 index 0000000..ca6f30d --- /dev/null +++ b/layout/group.html @@ -0,0 +1,477 @@ +{% extends "base.html" %} +{% block title %}Group{% endblock %} +{% block style_page %} + a { color: var(--color-primary); text-decoration: none; } + a:hover { text-decoration: underline; } + + h2 { + font-family: 'Inter', system-ui, sans-serif; + font-size: 1.20em; + font-weight: 700; + letter-spacing: 0.01em; + text-transform: none; + color: var(--color-heading); + margin-top: var(--section-gap); + margin-bottom: var(--heading-gap); + } + + h2:first-of-type { + margin-top: 0; + } + + h3 { + font-family: 'Inter', system-ui, sans-serif; + font-size: 1.20em; + font-weight: 700; + letter-spacing: 0.01em; + text-transform: none; + color: var(--color-heading); + margin-top: var(--section-gap); + margin-bottom: var(--heading-gap); + } + + p { + font-size: var(--text-body); + line-height: var(--leading-body); + color: var(--color-body); + } + + /* Member grid */ + .member-list { + list-style: none; + padding: 0; + display: grid; + grid-template-columns: repeat(4, 140px); + justify-content: center; + gap: 34px 34px; + } + + .member-item { + text-align: center; + } + + .member-item img { + width: 140px; + max-width: none; + height: auto; + aspect-ratio: 1/1; + object-fit: cover; + border-radius: 6px; + border: 1.5px solid var(--img-border); + margin-bottom: 8px; + } + + /* Arriving from a face elsewhere on the site (group.html#) */ + @keyframes member-highlight { + from { box-shadow: 0 0 0 4px var(--color-accent); } + to { box-shadow: 0 0 0 4px transparent; } + } + + .member-highlight img { + animation: member-highlight 2.4s ease-out; + } + + .member-item span { + display: block; + font-size: 0.88rem; + color: var(--color-body); + } + + .member-item .member-position { + font-size: 0.72rem; + color: var(--color-muted); + margin-top: 2px; + } + + .member-item a { + display: block; + font-size: 0.88rem; + color: var(--color-primary); + text-decoration: none; + } + + .member-item a:hover { text-decoration: underline; } + @media (max-width: 760px) { + .member-list { + grid-template-columns: repeat(3, 140px); + } + } + @media (max-width: 560px) { + .member-list { + grid-template-columns: repeat(2, 140px); + } + } + @media (max-width: 360px) { + .member-list { + grid-template-columns: repeat(1, 140px); + } + } + + /* Gallery preview */ + .gallery-preview-shell { + margin-top: 14px; + margin-bottom: 10px; + } + + .gallery-preview-frame { + display: grid; + grid-template-columns: auto minmax(0, 1fr) auto; + align-items: center; + gap: 12px; + } + + .gallery-preview-nav { + appearance: none; + border: 0; + background: transparent; + color: transparent; + width: 34px; + height: 72px; + padding: 0; + cursor: pointer; + position: relative; + transition: opacity 0.2s; + } + + .gallery-preview-nav::before { + content: ""; + position: absolute; + top: 50%; + left: 50%; + width: 18px; + height: 18px; + border-top: 3px solid #8d8d8d; + border-right: 3px solid #8d8d8d; + transition: border-color 0.2s; + } + + .gallery-preview-nav[data-gallery-dir="prev"]::before { + transform: translate(-30%, -50%) rotate(-135deg); + } + + .gallery-preview-nav[data-gallery-dir="next"]::before { + transform: translate(-70%, -50%) rotate(45deg); + } + + .gallery-preview-nav:hover:not(:disabled) { + color: transparent; + } + + .gallery-preview-nav:hover:not(:disabled)::before { + border-color: #5f5f5f; + } + + .gallery-preview-nav:disabled { + opacity: 0.35; + cursor: default; + } + + .gallery-preview { + display: grid; + grid-template-columns: repeat(5, 1fr); + gap: 4px; + } + + .gallery-preview-item { + position: relative; + overflow: hidden; + border: 1.5px solid var(--img-border); + border-radius: 6px; + aspect-ratio: 1; + } + + .gallery-preview-item img { + width: 100%; + height: 100%; + object-fit: cover; + display: block; + transition: opacity 0.2s; + } + + .gallery-preview-item:hover img { opacity: 1; } + + .gallery-preview-item .tooltip { + position: absolute; + bottom: 0; + left: 0; + right: 0; + background: rgba(0, 0, 0, 0.65); + color: var(--color-bg); + font-size: 0.7em; + text-align: center; + padding: 4px 6px; + opacity: 0; + transition: opacity 0.2s; + pointer-events: none; + } + + .gallery-preview-item:hover .tooltip { opacity: 1; } + + @media (max-width: 700px) { + .gallery-preview-frame { + grid-template-columns: 1fr; + gap: 10px; + } + + .gallery-preview-shell .gallery-preview-nav[data-gallery-dir="prev"] { + grid-row: 2; + justify-self: start; + } + + .gallery-preview-shell .gallery-preview-nav[data-gallery-dir="next"] { + grid-row: 2; + justify-self: end; + } + + .gallery-preview { + grid-template-columns: repeat(3, 1fr); + } + } + + @media (max-width: 420px) { + .gallery-preview { + grid-template-columns: repeat(2, 1fr); + } + } +{% endblock %} +{% block style_after_footer %} + @media (max-width: 900px) { + :root { + --page-pad-top: 28px; + --page-pad-x: 18px; + --page-pad-bottom: 40px; + --header-pad-bottom: 4px; + --header-gap: 16px; + --footer-logo-h: 18px; + } + + header { + display: grid; + grid-template-columns: minmax(0, 1fr) auto; + align-items: start; + column-gap: 10px; + } + + .lab-name { + min-width: 0; + } + + .menu-toggle { + display: block; + } + + nav { + display: none; + } + } +{% endblock %} +{% block style_after_drawer %} + /* Alumni & intern lists */ + .alumni-list, + .intern-list { + list-style: none; + padding: 0; + margin-top: 10px; + } + + .alumni-list li, + .intern-list li { + position: relative; + font-size: var(--text-body); + line-height: var(--leading-body); + color: var(--color-body); + padding-left: 16px; + margin-bottom: 6px; + } + + .alumni-list li::before, + .intern-list li::before { + content: "•"; + position: absolute; + left: 0; + color: var(--color-primary); /* 포인트 컬러 적용 */ + font-size: 1.2em; + line-height: 1.4; + } +{% endblock %} +{% block main %} +

Members

+
    + +{% for m in members.members %} +
  • + {{ m.alt }} +{% if m.url %} + {{ m.name }} +{% else %} + {{ m.name }} +{% endif %} + {{ m.position }} +
  • + +{% endfor %} +
+ +

Interns

+
    +
  • Minsung Kang (POSTECH)
  • +
  • Junkyu Park (POSTECH)
  • +
  • Wuvin An (POSTECH)
  • +
+ +

Gallery

+

We also spend time together on various occasions. For more stories, check out here!

+ + +{% if members.config.alumni.show %} +

{{ members.config.alumni.heading }}

+
    +{% for a in members.alumni %} +
  • {% if a.url %}{{ a.name }}{% else %}{{ a.name }}{% endif %} {{ a.note }}
  • +{% endfor %} +
+{% endif %} + +{% endblock %} +{% block scripts %} + +{% endblock %} diff --git a/layout/index.html b/layout/index.html new file mode 100644 index 0000000..5e4ed6d --- /dev/null +++ b/layout/index.html @@ -0,0 +1,419 @@ +{% extends "base.html" %} +{% block title %}Home{% endblock %} +{% block style_page %} + main img.lab-photo { + width: 100%; + height: auto; + display: block; + margin-bottom: 24px; + border-radius: 0; + } + + .intro { + font-size: var(--text-body); + font-weight: 300; + line-height: var(--leading-body); + color: var(--color-body); + margin-bottom: var(--section-gap); + } + + .intro a { + color: var(--color-primary); + text-decoration: none; + } + .intro a:hover { + text-decoration: underline; + } + + h3 { + font-family: "Inter", system-ui, sans-serif; + font-size: 1.20em; + font-weight: 700; + letter-spacing: 0.01em; + text-transform: none; + color: var(--color-heading); + margin-top: var(--section-gap); + margin-bottom: var(--heading-gap); + } + + h3:first-of-type { + margin-top: 0; + } + + .news-list { + margin: 0; + padding: 0; + } + + .news-item { + display: flex; + gap: 20px; + align-items: baseline; + padding: 10px 0; + } + + .news-item:last-child { + margin-bottom: 0; + } + + .news-date { + font-size: 0.78em; + font-weight: 400; + font-variant-numeric: tabular-nums; + font-feature-settings: "tnum"; + color: var(--color-heading); + min-width: 64px; + flex-shrink: 0; + display: flex; + flex-direction: column; + gap: 8px; + } + + .news-content { + font-size: var(--text-body); + font-weight: 300; + color: var(--color-body); + line-height: var(--leading-body); + } + + /* Member faces under a news item's date (rendered by tools/build.py) */ + .news-people { + display: flex; + gap: 2px; + } + + .member-face { + width: 20px; + height: 20px; + border-radius: 5px; + box-shadow: 0 0 0 0.5px var(--img-border); + overflow: hidden; + background: var(--color-bg); + } + + a.member-face:hover { + box-shadow: 0 0 0 1.5px var(--color-accent); + } + + /* Headshots have room above and around the face; zoom in on it. */ + .member-face img { + display: block; + width: 100%; + height: 100%; + object-fit: cover; + transform: scale(1.25); + transform-origin: 50% 30%; + } + + .member-face-more { + display: flex; + align-items: center; + justify-content: center; + font-size: 9px; + font-weight: 500; + color: var(--color-muted); + background: var(--rule-color); + } + + p.news-note { + font-size: var(--text-body); + font-weight: 300; + color: var(--color-body); + line-height: var(--leading-body); + margin-top: var(--para-gap); + } + + .news-note a { + color: var(--color-primary); + text-decoration: none; + } + + .news-note a:hover { + text-decoration: underline; + } + + .news-content a { + color: var(--color-primary); + text-decoration: none; + } + .news-content a:hover { + text-decoration: underline; + } + .news-content em { + font-style: normal; + font-weight: 600; + color: inherit; + } + + p.ack { + font-size: var(--text-body); + font-weight: 300; + color: var(--color-body); + line-height: var(--leading-body); + margin-top: 0; + } + + .sponsor-logos { + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: center; + column-gap: 20px; + row-gap: 10px; + margin-top: 20px; + background: var(--color-bg); + border-radius: 6px; + border: 1px solid var(--rule-color); + padding: 10px 16px; + width: fit-content; + margin-left: auto; + margin-right: auto; + } + + .sponsor-card { + display: flex; + align-items: center; + justify-content: center; + } + + .sponsor-card img { + display: block; + width: auto; + height: 34px; + object-fit: contain; + } + + .sponsor-card img.compact-logo { + height: 30px; + } + + .sponsor-card img.funding-mark { + height: 28px; + } + + .sponsor-card img.google-logo { + height: 28px; + } + + .sponsor-card img.etri-logo { + height: 26px; + } + + .sponsor-card img.epfl-logo { + height: 20px; + } +{% endblock %} +{% block style_after_footer %} + @media (max-width: 900px) { + :root { + --page-pad-top: 28px; + --page-pad-x: 18px; + --page-pad-bottom: 40px; + --header-pad-bottom: 4px; + --header-gap: 16px; + --footer-logo-h: 18px; + } + + header { + display: grid; + grid-template-columns: minmax(0, 1fr) auto; + align-items: start; + column-gap: 10px; + } + + .lab-name { + min-width: 0; + } + + .menu-toggle { + display: block; + } + + nav { + display: none; + } + } +{% endblock %} +{% block main %} + Lee Optimization Group members together outdoors by a lake in autumn + +

+ We pursue research in machine learning and optimization. To this end, + we develop theories and algorithms using computational and + mathematical tools. Our ultimate goal is to provide robust and + provable solutions to challenging problems in artificial intelligence, + particularly those in large-scale settings. We are passionate about + translating our findings into practical applications that can benefit + society. For further details on our research directions and ongoing + projects, please refer to the + Research. +

+ +

Recent News

+
+ +{% for n in news.home %} +
+ {{ n.date_label }}{{ n.faces }} + {{ n.html }} +
+ +{% endfor %} + + + + + + + + +
+ +

If you want to see our earlier news, check out here!

+ + + +{% endblock %} +{% block scripts %} + + +{% endblock %} diff --git a/layout/news.html b/layout/news.html new file mode 100644 index 0000000..3f6ccef --- /dev/null +++ b/layout/news.html @@ -0,0 +1,220 @@ +{% extends "base.html" %} +{% block title %}News{% endblock %} +{% block style_page %} + a { color: var(--color-primary); text-decoration: none; } + a:hover { text-decoration: underline; } + + p { + font-size: var(--text-body); + line-height: var(--leading-body); + color: var(--color-body); + margin-bottom: 0; + } + + /* News list */ + .news-list { + margin: 0; + padding: 0; + } + + .news-item { + display: flex; + gap: 20px; + align-items: baseline; + padding: 10px 0; + } + + .news-date { + font-size: 0.78em; + font-weight: 400; + font-variant-numeric: tabular-nums; + font-feature-settings: "tnum"; + color: var(--color-heading); + min-width: 64px; + flex-shrink: 0; + display: flex; + flex-direction: column; + gap: 8px; + } + + .news-content { + font-size: var(--text-body); + font-weight: 300; + color: var(--color-body); + line-height: var(--leading-body); + } + + /* Member faces under a news item's date (rendered by tools/build.py) */ + .news-people { + display: flex; + gap: 2px; + } + + .member-face { + width: 20px; + height: 20px; + border-radius: 5px; + box-shadow: 0 0 0 0.5px var(--img-border); + overflow: hidden; + background: var(--color-bg); + } + + a.member-face:hover { + box-shadow: 0 0 0 1.5px var(--color-accent); + } + + /* Headshots have room above and around the face; zoom in on it. */ + .member-face img { + display: block; + width: 100%; + height: 100%; + object-fit: cover; + transform: scale(1.25); + transform-origin: 50% 30%; + } + + .member-face-more { + display: flex; + align-items: center; + justify-content: center; + font-size: 9px; + font-weight: 500; + color: var(--color-muted); + background: var(--rule-color); + } + + .news-item[hidden], + .news-more[hidden] { + display: none; + } + + .news-more { + display: block; + width: 100%; + min-height: 44px; + margin-top: var(--heading-gap); + padding: 12px 0; + border: 0; + background: transparent; + color: var(--color-muted); + font: inherit; + font-size: 0.85em; + cursor: pointer; + } + + .news-more:hover { + color: var(--color-primary); + } + + .news-more::after { + content: ""; + display: inline-block; + width: 6px; + height: 6px; + margin-left: 8px; + vertical-align: 3px; + border-right: 1.5px solid currentColor; + border-bottom: 1.5px solid currentColor; + transform: rotate(45deg); + } + + .news-more:focus-visible, + .news-item:focus-visible { + outline: 2px solid var(--color-accent); + outline-offset: 3px; + } +{% endblock %} +{% block style_after_footer %} + @media (max-width: 900px) { + :root { + --page-pad-top: 28px; + --page-pad-x: 18px; + --page-pad-bottom: 40px; + --header-pad-bottom: 4px; + --header-gap: 16px; + --footer-logo-h: 18px; + } + + header { + display: grid; + grid-template-columns: minmax(0, 1fr) auto; + align-items: start; + column-gap: 10px; + } + + .lab-name { + min-width: 0; + } + + .menu-toggle { + display: block; + } + + nav { + display: none; + } + } +{% endblock %} +{% block main %} +
+ +{% for n in news.listed %} +
+ {{ n.date_label }}{{ n.faces }} + {{ n.html }} +
+ +{% endfor %} +
+ + +{% endblock %} +{% block scripts %} + +{% endblock %} diff --git a/layout/page.html b/layout/page.html new file mode 100644 index 0000000..982e287 --- /dev/null +++ b/layout/page.html @@ -0,0 +1,17 @@ +{#- Layout for the pages whose text lives in content/.html (research, + hiring, contact). The page's own CSS and script, if any, are + layout/styles/.css and layout/scripts/.js. -#} +{% extends "base.html" %} +{% block title %}{{ title }}{% endblock %} +{% block style_after_footer %} +{% include "styles/" ~ page ~ ".css" ignore missing %} +{% endblock %} +{% block main %} +{{ content }} +{% endblock %} +{% block scripts %} + +{% endblock %} diff --git a/layout/partials/chassis.css b/layout/partials/chassis.css new file mode 100644 index 0000000..6410146 --- /dev/null +++ b/layout/partials/chassis.css @@ -0,0 +1,131 @@ + *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } + + /* ------------------------------------------------------------------ + Layout tokens — single source of truth for spacing on this page. + This block is identical on every page; keep all pages in sync. + ------------------------------------------------------------------ */ + :root { + /* page frame */ + --content-max: 800px; + --page-pad-top: clamp(24px, 5vw, 48px); + --page-pad-x: clamp(16px, 4vw, 32px); + --page-pad-bottom: clamp(36px, 5vw, 60px); + + /* header / nav */ + --header-pad-bottom: 28px; + --header-gap: 40px; + --lab-name-gap: 18px; + --nav-gap: clamp(10px, 3vw, 24px); + + /* vertical rhythm */ + --section-gap: 40px; /* space above a section heading */ + --heading-gap: 14px; /* space below a section heading */ + --para-gap: 14px; /* space between stacked paragraphs */ + + /* footer */ + --footer-gap: 52px; + --footer-pad-top: 20px; + --footer-logo-gap: 14px; + --footer-logo-h: 28px; + + /* body type */ + --text-body: 0.93em; + --leading-body: 1.85; + + /* rules & lines */ + --rule-color: #e2e6f0; + + /* colour palette */ + --color-bg: #ffffff; + --color-heading: #111; + --color-body: #374151; + --color-muted: #6b7280; + --color-primary: #1B3A8A; + --color-accent: #3B6FD4; + --color-emphasis: #d6423a; + --color-highlight-bg: #f7f1dc; + + /* surfaces & borders */ + --img-border: #b8bec8; + --control-border: #d3d9e6; + --surface-subtle: #f4f6fa; + --surface-hover: #e6ecf9; + + /* research area categories */ + --cat-compression: #d6423a; + --cat-distributed: #27ae60; + --cat-applications: #2e6bd9; + --cat-optimization: #f39c12; + } + + html { + overflow-y: scroll; + } + + body { + font-family: 'Inter', system-ui, sans-serif; + color: var(--color-heading); + background: var(--color-bg); + min-height: 100vh; + } + + .inner { + max-width: var(--content-max); + margin: 0 auto; + padding: var(--page-pad-top) var(--page-pad-x) var(--page-pad-bottom); + } + + header { + padding-bottom: var(--header-pad-bottom); + margin-bottom: var(--header-gap); + border-bottom: 1px solid var(--rule-color); + } + + .lab-name { + font-family: 'EB Garamond', Georgia, serif; + font-size: 2.8em; + font-weight: 500; + color: var(--color-heading); + letter-spacing: 0; + line-height: 0.96; + display: block; + margin-bottom: var(--lab-name-gap); + text-decoration: none; + } + + .lab-name:hover { + text-decoration: none; + color: var(--color-heading); + } + + nav { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: var(--nav-gap); + } + + nav a { + text-decoration: none; + color: var(--color-muted); + font-size: 1em; + font-weight: 400; + letter-spacing: 0.01em; + padding-bottom: 2px; + border-bottom: 1.5px dashed transparent; + transition: color 0.15s, border-color 0.15s; + } + + nav a:hover { + color: var(--color-primary); + border-bottom: 1.5px solid var(--color-primary); + text-decoration: none; + } + + nav a.active { + color: var(--color-heading); + font-weight: 600; + border-bottom: 1.5px solid var(--color-heading); + } + + .nav-drawer a.active { color: var(--color-heading); font-weight: 600; } diff --git a/layout/partials/drawer.css b/layout/partials/drawer.css new file mode 100644 index 0000000..cb89a99 --- /dev/null +++ b/layout/partials/drawer.css @@ -0,0 +1,55 @@ + /* Drawer */ + .nav-overlay { + display: none; + position: fixed; + inset: 0; + background: rgba(0,0,0,0.3); + z-index: 999; + } + + .nav-drawer { + position: fixed; + top: 0; + right: -280px; + width: 240px; + height: 100vh; + background: var(--color-bg); + box-shadow: -4px 0 24px rgba(0,0,0,0.12); + z-index: 1000; + transition: right 0.3s ease; + padding: 64px 32px 40px; + display: flex; + flex-direction: column; + gap: 20px; + } + + .nav-drawer.open { + right: 0; + } + + .nav-overlay.open { + display: block; + } + + .drawer-close { + position: absolute; + top: 20px; + right: 20px; + background: none; + border: none; + font-size: 1.4em; + cursor: pointer; + color: var(--color-body); + line-height: 1; + } + + .nav-drawer a { + text-decoration: none; + color: var(--color-body); + font-size: 1.05em; + font-weight: 400; + } + + .nav-drawer a:hover { + color: var(--color-primary); + } diff --git a/layout/partials/footer.css b/layout/partials/footer.css new file mode 100644 index 0000000..e1d7c06 --- /dev/null +++ b/layout/partials/footer.css @@ -0,0 +1,35 @@ + footer { + margin-top: var(--footer-gap); + padding-top: var(--footer-pad-top); + border-top: 1px solid var(--rule-color); + display: flex; + align-items: center; + justify-content: space-between; + font-size: 0.75em; + color: var(--color-muted); + } + + .footer-logos { + display: flex; + align-items: center; + gap: var(--footer-logo-gap); + } + + .footer-logos img { + height: var(--footer-logo-h); + width: auto; + object-fit: contain; + opacity: 1; + } + + .menu-toggle { + display: none; + background: var(--color-bg); + border: 1px solid var(--rule-color); + border-radius: 6px; + font-size: 1.3em; + cursor: pointer; + color: var(--color-body); + padding: 4px 8px; + line-height: 1; + } diff --git a/photo.html b/layout/photo.html similarity index 58% rename from photo.html rename to layout/photo.html index a5cdeeb..138a841 100644 --- a/photo.html +++ b/layout/photo.html @@ -1,144 +1,6 @@ - - - - - - - Gallery Photo | Lee Optimization Group - - - - - -
- -
- Lee Optimization Group - - -
- -
+{% endblock %} +{% block main %}

Gallery Photo

@@ -461,30 +214,9 @@

Gallery Photo

-
- -
- © 2026 LOG. All rights reserved. - -
- -
- - - - + +{% endblock %} +{% block scripts %} - - +{% endblock %} diff --git a/layout/publications.html b/layout/publications.html new file mode 100644 index 0000000..f6045a1 --- /dev/null +++ b/layout/publications.html @@ -0,0 +1,168 @@ +{% extends "base.html" %} +{% block title %}Publications{% endblock %} +{% block style_page %} + a { color: var(--color-primary); text-decoration: none; } + a:hover { text-decoration: underline; } + + .year-divider { + font-family: 'Inter', system-ui, sans-serif; + font-size: 1.20em; + font-weight: 700; + letter-spacing: 0.01em; + color: var(--color-heading); + margin-top: var(--section-gap); + margin-bottom: var(--heading-gap); + } + + .year-divider:first-of-type { + margin-top: 0; + } + + .paper { + margin-bottom: 18px; + padding-left: 14px; + border-left: 2px solid var(--rule-color); + } + + .paper-title { + font-size: 0.94em; + font-weight: 500; + color: var(--color-heading); + line-height: 1.56; + } + + .paper-links { + font-size: 0.8em; + margin-top: 5px; + } + + .paper-links a { + color: var(--color-primary); + text-decoration: none; + margin-right: 4px; + } + + .paper-links a:hover { text-decoration: underline; } + + .paper-authors { + font-size: 0.86em; + color: var(--color-body); + margin-top: 4px; + } + + /* Faces before current members in author lists; rendered by tools/build.py. + Face + name link to the member's site, or their card on group.html. */ + .paper-member { + white-space: nowrap; + color: var(--color-heading); + font-weight: 500; + text-decoration: none; + } + + .paper-member:hover { + color: var(--color-primary); + } + + .member-face { + display: inline-block; + width: 18px; + height: 18px; + margin-right: 4px; + vertical-align: -4px; + border-radius: 4px; + box-shadow: 0 0 0 0.5px var(--img-border); + overflow: hidden; + background: var(--color-bg); + } + + /* Headshots have room above and around the face; zoom in on it. */ + .member-face img { + display: block; + width: 100%; + height: 100%; + object-fit: cover; + transform: scale(1.25); + transform-origin: 50% 30%; + } + + .paper-venue { + font-size: 0.82em; + color: var(--color-muted); + margin-top: 2px; + } + + .spotlight { + color: var(--color-emphasis); + } +{% endblock %} +{% block style_after_footer %} + @media (max-width: 900px) { + :root { + --page-pad-top: 28px; + --page-pad-x: 18px; + --page-pad-bottom: 40px; + --header-pad-bottom: 4px; + --header-gap: 16px; + --footer-logo-h: 18px; + } + + header { + display: grid; + grid-template-columns: minmax(0, 1fr) auto; + align-items: start; + column-gap: 10px; + } + + .lab-name { + min-width: 0; + } + + .menu-toggle { + display: block; + } + + nav { + display: none; + } + } +{% endblock %} +{% block main %} +{% for g in publications.groups %} +
{{ g.label }}
+ +{% for p in g.papers %} +
+
{{ p.title }}
+
{{ p.authors }}
+
{{ p.venue }}
+{% if p.links %} + +{% endif %} +
+ +{% endfor %} +{% endfor %} + +{% endblock %} +{% block scripts %} + +{% endblock %} diff --git a/layout/scripts/drawer.js b/layout/scripts/drawer.js new file mode 100644 index 0000000..5ae1461 --- /dev/null +++ b/layout/scripts/drawer.js @@ -0,0 +1,17 @@ + var toggle = document.querySelector('.menu-toggle'); + var drawer = document.getElementById('navDrawer'); + var overlay = document.getElementById('navOverlay'); + var close = document.getElementById('drawerClose'); + + function openDrawer() { + drawer.classList.add('open'); + overlay.classList.add('open'); + } + function closeDrawer() { + drawer.classList.remove('open'); + overlay.classList.remove('open'); + } + + toggle.addEventListener('click', function() { drawer.classList.contains('open') ? closeDrawer() : openDrawer(); }); + close.addEventListener('click', closeDrawer); + overlay.addEventListener('click', closeDrawer); diff --git a/layout/scripts/research.js b/layout/scripts/research.js new file mode 100644 index 0000000..6e536c8 --- /dev/null +++ b/layout/scripts/research.js @@ -0,0 +1,71 @@ + document.querySelectorAll('.fold-trigger').forEach(function(button) { + button.addEventListener('click', function() { + var detail = document.getElementById(button.getAttribute('aria-controls')); + var isOpen = button.getAttribute('aria-expanded') === 'true'; + button.setAttribute('aria-expanded', String(!isOpen)); + if (detail) { + detail.classList.toggle('open', !isOpen); + } + }); + }); + + function applyConditionalResearchWrap() { + var sections = Array.prototype.filter.call(document.querySelectorAll('.section'), function(section) { + return section.querySelector('img') && section.querySelector('.section-text p'); + }); + if (!sections.length) { + return; + } + + sections.forEach(function(section) { + section.classList.remove('wrap-flow'); + }); + + // One square size for every icon, 90% as tall as the shortest paragraph. + // Resizing the icons reflows the text, so repeat a few times to converge; + // capped so narrow screens fall back to the wrap-flow layout below. + var maxSize = Math.min(200, sections[0].getBoundingClientRect().width * 0.4); + for (var i = 0; i < 4; i++) { + var shortest = Infinity; + sections.forEach(function(section) { + shortest = Math.min(shortest, section.querySelector('.section-text p').getBoundingClientRect().height); + }); + var size = Math.min(Math.max(shortest * 0.9, 80), maxSize); + sections.forEach(function(section) { + var image = section.querySelector('img'); + image.style.width = size + 'px'; + image.style.height = size + 'px'; + }); + } + + sections.forEach(function(section) { + var image = section.querySelector('img'); + var textBlock = section.querySelector('.section-text p'); + + var textStyle = window.getComputedStyle(textBlock); + var lineHeight = parseFloat(textStyle.lineHeight); + if (!lineHeight || Number.isNaN(lineHeight)) { + lineHeight = parseFloat(textStyle.fontSize) * 1.75; + } + + var textHeight = textBlock.getBoundingClientRect().height; + var imageHeight = image.getBoundingClientRect().height; + var threshold = imageHeight + (2 * lineHeight); + + if (textHeight > threshold) { + section.classList.add('wrap-flow'); + } + }); + } + + var wrapResizeTimer; + function onWrapResize() { + window.clearTimeout(wrapResizeTimer); + wrapResizeTimer = window.setTimeout(applyConditionalResearchWrap, 80); + } + + window.addEventListener('load', applyConditionalResearchWrap); + window.addEventListener('resize', onWrapResize); + if (document.fonts && document.fonts.ready) { + document.fonts.ready.then(applyConditionalResearchWrap); + } diff --git a/layout/styles/contact.css b/layout/styles/contact.css new file mode 100644 index 0000000..d4186cd --- /dev/null +++ b/layout/styles/contact.css @@ -0,0 +1,81 @@ + a { color: var(--color-primary); text-decoration: none; } + a:hover { text-decoration: underline; } + + .contact-title { + color: var(--color-heading); + font-weight: 700; + font-size: 1.20em; + letter-spacing: 0.01em; + } + + p { + font-size: var(--text-body); + line-height: var(--leading-body); + color: var(--color-body); + margin-bottom: 0; + } + + .contact-layout { + display: grid; + grid-template-columns: minmax(0, 1fr) minmax(0, 0.8fr); + gap: 28px; + align-items: start; + } + + .contact-map { + position: relative; + top: 72px; + width: 100%; + max-width: 270px; + min-height: 170px; + aspect-ratio: 4 / 3; + border: 1px solid var(--rule-color); + border-radius: 6px; + overflow: hidden; + } + + .contact-map iframe { + display: block; + width: 100%; + height: 100%; + border: 0; + } + + @media (max-width: 900px) { + :root { + --page-pad-top: 28px; + --page-pad-x: 18px; + --page-pad-bottom: 40px; + --header-pad-bottom: 4px; + --header-gap: 16px; + --footer-logo-h: 18px; + } + + header { + display: grid; + grid-template-columns: minmax(0, 1fr) auto; + align-items: start; + column-gap: 10px; + } + + .lab-name { + min-width: 0; + } + + .menu-toggle { + display: block; + } + + nav { + display: none; + } + + .contact-layout { + grid-template-columns: minmax(0, 1fr); + gap: 20px; + } + + .contact-map { + top: 0; + } + } diff --git a/layout/styles/hiring.css b/layout/styles/hiring.css new file mode 100644 index 0000000..8eb4bf9 --- /dev/null +++ b/layout/styles/hiring.css @@ -0,0 +1,86 @@ + a { color: var(--color-primary); text-decoration: none; } + a:hover { text-decoration: underline; } + + h2 { + font-family: 'Inter', system-ui, sans-serif; + font-size: 1.20em; + font-weight: 700; + letter-spacing: 0.01em; + text-transform: none; + color: var(--color-heading); + margin-top: var(--section-gap); + margin-bottom: var(--heading-gap); + } + + h2:first-of-type { margin-top: 0; } + + h2.red-title { + color: var(--color-emphasis); + } + + p { + font-size: var(--text-body); + line-height: var(--leading-body); + color: var(--color-body); + margin-bottom: var(--para-gap); + } + + ul { + font-size: var(--text-body); + line-height: var(--leading-body); + color: var(--color-body); + padding-left: 20px; + margin-bottom: var(--para-gap); + } + + li { margin-bottom: 4px; } + + .red { color: var(--color-emphasis); font-weight: 600; } + + .highlight-text { + background-color: var(--color-highlight-bg); + padding: 2px 5px; + border-radius: 6px; + } + + .video-wrap { + margin: 16px 0 0; + width: 100%; + aspect-ratio: 16/9; + } + + .video-wrap iframe { + width: 100%; + height: 100%; + border: none; + } + + @media (max-width: 900px) { + :root { + --page-pad-top: 28px; + --page-pad-x: 18px; + --page-pad-bottom: 40px; + --header-pad-bottom: 4px; + --header-gap: 16px; + --footer-logo-h: 18px; + } + + header { + display: grid; + grid-template-columns: minmax(0, 1fr) auto; + align-items: start; + column-gap: 10px; + } + + .lab-name { + min-width: 0; + } + + .menu-toggle { + display: block; + } + + nav { + display: none; + } + } diff --git a/layout/styles/research.css b/layout/styles/research.css new file mode 100644 index 0000000..27d3e0e --- /dev/null +++ b/layout/styles/research.css @@ -0,0 +1,223 @@ + a { color: var(--color-primary); text-decoration: none; } + a:hover { text-decoration: underline; } + + h2 { + font-family: 'Inter', system-ui, sans-serif; + font-size: 1.20em; + font-weight: 700; + letter-spacing: 0.01em; + text-transform: none; + color: var(--color-heading); + margin-top: var(--section-gap); + margin-bottom: var(--heading-gap); + } + + h2:first-of-type { + margin-top: 0; + } + + .research-intro { + margin-bottom: 26px; + } + + p { + font-size: var(--text-body); + line-height: var(--leading-body); + color: var(--color-body); + margin-bottom: 0; + } + + .fold-trigger { + align-items: baseline; + background: none; + border: none; + color: var(--color-muted); + cursor: pointer; + display: inline-flex; + font-size: 0.82em; + font-weight: 500; + gap: 6px; + margin-left: 4px; + padding: 0; + } + + .fold-trigger:hover { + color: var(--color-primary); + } + + .fold-trigger:hover .fold-label { + text-decoration: underline; + text-underline-offset: 3px; + } + + .fold-trigger:focus { + outline: none; + } + + .fold-trigger:focus-visible { + outline: 1px solid currentColor; + outline-offset: 3px; + } + + .fold-caret { + font-size: 0.85em; + transition: transform 0.15s ease; + } + + .fold-trigger[aria-expanded="true"] .fold-caret { + transform: rotate(90deg); + } + + .fold-detail { + border-left: 2px solid var(--rule-color); + color: var(--color-body); + display: none; + font-size: 0.9em; + line-height: 1.68; + margin-top: 10px; + padding-left: 12px; + } + + .fold-detail.open { + display: block; + } + + .fold-item { + margin-bottom: 9px; + } + + .fold-item:last-child { + margin-bottom: 0; + } + + .fold-item-title { + color: var(--color-heading); + display: block; + font-weight: 600; + margin-bottom: 2px; + } + + .paper-tag { + color: var(--color-muted); + font-size: 0.82em; + font-weight: 400; + } + + .paper-tag a { + color: inherit; + font-weight: 500; + position: relative; + text-decoration: none; + transition: color 0.15s ease; + } + + .paper-tag a::after { + border-bottom: 1px dotted currentColor; + bottom: -2px; + content: ""; + left: 0; + position: absolute; + transform: scaleX(0); + transform-origin: left; + transition: transform 0.18s ease; + width: 100%; + } + + .paper-tag a:hover, + .paper-tag a:focus { + color: var(--color-primary); + outline: none; + text-decoration: none; + } + + .paper-tag a:hover::after, + .paper-tag a:focus::after { + transform: scaleX(1); + transition-duration: 0s; + } + + .section { + display: grid; + grid-template-columns: auto minmax(0, 1fr); + align-items: start; + column-gap: 22px; + row-gap: 10px; + margin-bottom: 40px; + } + + .section .section-text { + min-width: 0; + } + + .section img { + width: 165px; + max-width: none; + height: auto; + aspect-ratio: 1 / 1; + box-sizing: border-box; + padding: 12px; + object-fit: contain; + border: 1.5px solid var(--img-border); + border-radius: 6px; + } + + /* Round shapes read smaller than square ones at the same size, so + these icons get less inset inside the same frame. */ + .section img.icon-round { + padding: 7px; + } + + .section.wrap-flow { + display: block; + } + + .section.wrap-flow::after { + content: ""; + display: block; + clear: both; + } + + .section.wrap-flow img { + float: left; + margin: 0 22px 10px 0; + } + + .section.wrap-flow .section-text { + display: block; + } + + .section-title { font-weight: 700; } + .red { color: var(--cat-compression); font-weight: 600; } + .orange { color: var(--cat-optimization); font-weight: 600; } + .green { color: var(--cat-distributed); font-weight: 600; } + .blue { color: var(--cat-applications); font-weight: 600; } + + @media (max-width: 900px) { + :root { + --page-pad-top: 28px; + --page-pad-x: 18px; + --page-pad-bottom: 40px; + --header-pad-bottom: 4px; + --header-gap: 16px; + --footer-logo-h: 18px; + } + + header { + display: grid; + grid-template-columns: minmax(0, 1fr) auto; + align-items: start; + column-gap: 10px; + } + + .lab-name { + min-width: 0; + } + + .menu-toggle { + display: block; + } + + nav { + display: none; + } + } diff --git a/news.html b/news.html deleted file mode 100644 index de372ea..0000000 --- a/news.html +++ /dev/null @@ -1,550 +0,0 @@ - - - - - - - News | Lee Optimization Group - - - - - -
- -
- Lee Optimization Group - - -
- -
-
- -
- Oct 2026 - 🎓 Our preprint on decentralized LLM training, SeedFlood, has received a major update, with substantial improvements to its overall presentation, analysis, and experimental evaluation. -
- - -
- Oct 2026 - 🧑‍💻 Minkyoung has started a research internship at Nota AI. -
- -
- Oct 2026 - 🎓 Our paper on hardware-native sparse-quantization for trillion-scale MoEs is out on arXiv. In this work, we introduce MoESQ, which jointly learns semi-structured sparsity and low-precision quantization for MoE experts and executes them on Sparse Tensor Cores with a custom grouped sparse GEMM kernel. The code and models are publicly available. -
- -
- Sep 2026 - 🎓 Our paper on decentralized training has been accepted to NeurIPS 2026 Workshop on Collaborative, Open, and Decentralized Training of Foundation Models, selected as an oral presentation. In this work, we introduce a novel decentralized LLM fine-tuning framework that achieves extreme communiation efficiency and global consensus. -
- -
- Sep 2026 - 🎓 Our paper on asynchronous pipeline parallelism has been accepted to NeurIPS 2026. In this work, we introduce FRESCO, a new principle for consistency control in asynchronous pipelines, which consistently outperforms standard baselines while being extremely efficient and theoretically sound. -
- -
- Sep 2026 - 🎓 Namhoon is serving as an area chair for ICLR 2027. -
- -
- Aug 2026 - 🏆 Hyunji, Hyeondo, and Jinseok have been selected for the NRF Research Grant for Master’s and PhD Students. -
- -
- Aug 2026 - 🏛 We are co-organizing the LeT Workshop on October 7~8 at POSTECH. The workshop aims to bring together researchers interested in machine learning theory. -
- -
- Aug 2026 - 🎓 Our paper on extreme LLM sparsity has been accepted to EMNLP 2026. In this work, we show that carefully handling the elementary components of pruning can already substantially outperform existing pruning methods, even at extreme sparsity. -
- -
- Aug 2026 - 🎓 Our paper on semiconductor anomaly detection has been accepted to BMVC 2026. In this work, we introduce a semiconductor defect dataset (OASIS) along with vision-guided instruction tuning method. -
- -
- Jul 2026 - 🤝 Our lab has been selected for the 2026 NRF Next-Generation AI for Mathematics program. -
- -
- Jul 2026 - 🏆 Kwanhee received the IITP President’s Award in the Graduate Student Outstanding Research Achievement Competition at AI Connect Korea 2026. -
- -
- Jul 2026 - 🏆 Our new paper on extreme LLM sparsity has received the Best Paper Award at CKAIA 2026. The preprint will be available on arXiv soon. -
- -
- Jul 2026 - 🏆 Sungbin has been selected for the 2026 PhD Excellence Scholarship in Science and Engineering. -
- -
- Jul 2026 - 🤝 Our lab joined the National AI Research Lab (NAIRL) academic consortium on July 1, 2026. -
- -
- Jun 2026 - 🎓 Our paper on flatness-based out-of-distribution detection has been accepted to ECCV 2026. In this work, we propose FOLD, a lightweight detector that leverages feature-Hessian curvature and partial feature normalization to improve ID-OOD separability. -
- -
- May 2026 - 🏆 Our paper on uncertainty quantification in ICL, accepted to ACL 2026, has been selected as an Oral presentation. In this work, we introduce self-function vectors to directly decompose uncertainty, and propose a novel framework to evaluate these disentangled sources. -
- -
- May 2026 - 🎓 Our paper on asynchronous pipeline parallelism has been accepted to ICML 2026. In this work, we propose a basis rotation approach to effectively address the associated gradient staleness problem. -
- -
- Apr 2026 - 🤝 Our lab has been selected for the IITP Efficient AI National R&D Program (2026–2029), a national research program with KRW 7.5B in total funding, together with leading partners including SqueezeBits. -
- -
- Feb 2026 - 🤝 Our lab has been selected for the "Early-Career Researcher Infrastructure Support Program" (NRF) — one of the most competitive grants in Korea. With 500M KRW in funding, we will build a high-performance compute infrastructure to power our research on extreme compression of hyperscale AI foundation models. -
- -
- Feb 2026 - ✈️ Kwanhee started as a visiting student researcher in the Dan Alistarh Group at ISTA. -
- -
- Jan 2026 - 🎓 Our paper on extreme LLM sparsity (ELSA) has been accepted to ICLR 2026. -
-
- -
- -
- © 2026 LOG. All rights reserved. - -
- -
- - - - - - - diff --git a/publications.html b/publications.html deleted file mode 100644 index d83678e..0000000 --- a/publications.html +++ /dev/null @@ -1,676 +0,0 @@ - - - - - - - Publications | Lee Optimization Group - - - - - -
- -
- Lee Optimization Group - - -
- -
- -
Preprint
- -
-
Hardware-Native Joint Sparse-Quantization for Trillion-Scale Mixture-of-Experts
-
Kwanhee Lee, Namhoon Lee, Dan Alistarh
-
arXiv 2026
- -
- - -
2026
- -
-
Comparing Synchronous and Asynchronous Pipeline Parallelism Across Training Configurations
-
Junkyu Park, Dongyeop Lee, Namhoon Lee
-
NeurIPS 2026 Workshop on Collaborative, Open, and Decentralized Training of Foundation Models
-
- -
-
SeedFlood: A Step Toward Scalable Decentralized Fine-Tuning of LLMs
-
Jihun Kim, Dongyeop Lee, Namhoon Lee
-
NeurIPS 2026 Workshop on Collaborative, Open, and Decentralized Training of Foundation Models (Oral)
- -
- -
-
FRESCO: A Novel Consistency Control for Asynchronous Pipeline Parallel Training
-
Dongyeop Lee, Namhoon Lee
-
NeurIPS 2026
-
- -
-
Hidden in Plain Sight: The Overlooked Significance of Canonical Elements for Extreme LLM Sparsity
-
Hyeondo Jang, Kwanhee Lee, Dongyeop Lee, Namhoon Lee
-
EMNLP 2026; CKAIA 2026 Best Paper Award
- -
- -
-
Advancing Semiconductor Inspection: A New Dataset and Approach for Robust Anomaly Detection with Large Vision Language Models
-
Jaehyeon Jeong*, Hyeondo Jang*, Donghyun Oh, Seongeun Kim, Namhoon Lee
-
BMVC 2026
-
- -
-
Exploiting Local Flatness for Efficient Out-of-Distribution Detection
-
Seonghwan Park, Hyunji Jung, Dongyeop Lee, Namhoon Lee
-
ECCV 2026
- -
- -
-
On-Policy Adaptation Mitigates Hyperparameter-Sensitive Forgetting in Vision-Language Models
-
Doyoon Kim*, Ziyang Wu*, Jingfeng Yang, Yi Ma
-
ICML 2026 Workshop on Continual Adaptation at Scale: Towards Sustainable AI
- -
- -
-
Mitigating Staleness in Asynchronous Pipeline Parallelism via Basis Rotation
-
Hyunji Jung*, Sungbin Shin*, Namhoon Lee
-
ICML 2026
- -
- -
-
Quantifying Aleatoric Uncertainty of In-Context Learning for Robust Measure of LLM Prediction Confidence
-
Jinseok Chung, Minkyoung Song*, Hyunji Jung*, Namhoon Lee
-
ACL 2026 (Oral); KCC 2026 Workshop Outstanding Paper Award
- -
- -
-
Dynamic Network Compression via Probabilistic Channel Pruning
-
Kwanhee Lee, Hyang Won Lee
-
Neural Networks 2026
- -
- -
-
The Unseen Frontier: Pushing the Limits of LLM Sparsity with Surrogate-Free ADMM
-
Kwanhee Lee, Hyeondo Jang, Dongyeop Lee, Dan Alistarh, Namhoon Lee
-
ICLR 2026; JKAIA 2025 Best Paper Award
- -
- - -
2025
- -
-
MemEIC: A Step Toward Continual and Compositional Knowledge Editing
-
Jin Seong*, Jiyun Park*, Wencke Liermann, Hongseok Choi, Yoonji Nam, Hyun Kim, Soojong Lim, Namhoon Lee
-
NeurIPS 2025
- -
- -
-
An Analysis of Concept Bottleneck Models: Measuring, Understanding, and Mitigating the Impact of Noisy Annotations
-
Seonghwan Park, Jueun Mun, Donghyun Oh, Namhoon Lee
-
NeurIPS 2025
- -
- -
-
Critical Influence of Overparameterization on Sharpness-aware Minimization
-
Sungbin Shin*, Dongyeop Lee*, Maksym Andriushchenko, Namhoon Lee
-
UAI 2025; ICML 2023 Workshop on High-dimensional Learning Dynamics; JKAIA 2023 Best Paper Award
- -
- -
-
SAFE: Finding Sparse and Flat Minima to Improve Pruning
-
Dongyeop Lee, Kwanhee Lee, Jinseok Chung, Namhoon Lee
-
ICML 2025 (Spotlight)
- -
- -
-
SASSHA: Sharpness-aware Adaptive Second-order Optimization with Stable Hessian Approximation
-
Dahun Shin*, Dongyeop Lee*, Jinseok Chung, Namhoon Lee
-
ICML 2025
- -
- -
-
ZIP: An Efficient Zeroth-order Prompt Tuning for Black-box Vision-Language Models
-
Seonghwan Park, Jaehyeon Jeong, Yongjun Kim, Jaeho Lee, Namhoon Lee
-
ICLR 2025
- -
- -
-
Convergence and Implicit Bias of Gradient Descent on Continual Linear Classification
-
Hyunji Jung*, Hanseul Cho*, Chulhee Yun
-
ICLR 2025
- -
- - -
2024
- -
-
Rethinking Pruning Large Language Models: Benefits and Pitfalls of Reconstruction Error Minimization
-
Sungbin Shin, Wonpyo Park, Jaeho Lee, Namhoon Lee
-
EMNLP 2024
- -
- -
-
The Role of Masking for Efficient Supervised Knowledge Distillation of Vision Transformers
-
Seungwoo Son, Jegwang Ryu, Namhoon Lee, Jaeho Lee
-
ECCV 2024; ICLR 2023 Workshop
- -
- -
-
JaxPruner: A Concise Library for Sparsity Research
-
Joo Hyung Lee, Wonpyo Park, Nicole Mitchell, Jonathan Pilault, Johan Obando-Ceron, Han-Byul Kim, Namhoon Lee, Elias Frantar, Yun Long, Amir Yazdanbakhsh, Shivani Agrawal, Suvinay Subramanian, Xin Wang, Sheng-Chun Kao, Xingyao Zhang, Trevor Gale, Aart Bik, Woohyun Han, Milen Ferez, Zhonglin Han, Hong-Seok Kim, Yann Dauphin, Gintare Karolina Dziugaite, Pablo Samuel Castro, Utku Evci
-
CPAL 2024 (Oral); ICLR 2023 Workshop on Sparsity in Neural Networks
- -
- - -
2023
- -
-
Pruning Neural Networks with Velocity-Constrained Optimization
-
Donghyun Oh*, Jinseok Chung*, Namhoon Lee
-
NeurIPS 2023 Workshop on Optimization for Machine Learning
- -
- -
-
A Closer Look at the Intervention Procedure of Concept Bottleneck Models
-
Sungbin Shin, Yohan Jo, Sungsoo Ahn, Namhoon Lee
-
ICML 2023; NeurIPS 2022 Workshop on Trustworthy and Socially Responsible ML
- -
- -
-
On the Effectiveness of Sharpness-Aware Minimization with Large Mini-batches
-
Jinseok Chung, Seonghwan Park, Jaeho Lee, Namhoon Lee
-
ICML 2023 Workshop on High-dimensional Learning Dynamics
- -
- -
-
FedFwd: Federated Learning without Backpropagation
-
Seonghwan Park, Dahun Shin, Jinseok Chung, Namhoon Lee
-
ICML 2023 Workshop on Federated Learning and Analytics in Practice
- -
- -
-
Semi-Supervised Concept Bottleneck Models
-
Jeeon Bae, Sungbin Shin, Namhoon Lee
-
ICML 2023 Workshop on Artificial Intelligence and Human-Computer Interaction
- -
- -
-
Almost Sure Last Iterate Convergence of Sharpness-Aware Minimization
-
Kyungchun Nam, Jinseok Chung, Namhoon Lee
-
ICLR 2023 Tiny Papers
- -
- - -
Previous
- -
-
Meta-Learning Sparse Implicit Neural Representations
-
Jaeho Lee*, Jihoon Tack*, Namhoon Lee, Jinwoo Shin
-
NeurIPS 2021; SNN 2021
- -
- -
-
Understanding the Effects of Data Parallelism and Sparsity on Neural Network Training
-
Namhoon Lee, Thalaiyasingam Ajanthan, Philip H. S. Torr, Martin Jaggi
-
ICLR 2021
- -
- -
-
Optimal Mini-Batch Size for Stochastic Gradient Methods
-
Namhoon Lee, Philip H. S. Torr, Richard Hartley
-
ICLR 2021 Workshop on Science and Engineering of Deep Learning
- -
- -
-
A Signal Propagation Perspective for Pruning Neural Networks at Initialization
-
Namhoon Lee, Thalaiyasingam Ajanthan, Stephen Gould, Philip H. S. Torr
-
ICLR 2020 (Spotlight)
- -
- -
-
Data Parallelism in Training Sparse Neural Networks
-
Namhoon Lee, Philip H. S. Torr, Martin Jaggi
-
ICLR 2020 Workshop on Practical ML
- -
- -
-
SNIP: Single-shot Network Pruning based on Connection Sensitivity
-
Namhoon Lee, Thalaiyasingam Ajanthan, Philip H. S. Torr
-
ICLR 2019
- -
- -
-
Learn to Pay Attention
-
Saumya Jetley, Nicholas Lord, Namhoon Lee, Philip H. S. Torr
-
ICLR 2018
- -
- -
-
Synthesizing a Scene-Specific Pedestrian Detector and Pose Estimator for Static Video Surveillance
-
Hironori Hattori*, Namhoon Lee*, Vishnu N. Boddeti, Fares Beainy, Kris M. Kitani, Takeo Kanade
-
IJCV 2018
- -
- -
-
DESIRE: Distant Future Prediction in Dynamic Scenes with Interacting Agents
-
Namhoon Lee, Wongun Choi, Paul Vernaza, Christopher B. Choy, Philip H. S. Torr, Manmohan Chandraker
-
CVPR 2017 (Spotlight)
- -
- -
-
Forecasting Interactive Dynamics of Pedestrians with Fictitious Play
-
Wei-Chiu Ma, De-An Huang, Namhoon Lee, Kris M. Kitani
-
CVPR 2017
- -
- -
-
Predicting Wide Receiver Trajectories in American Football
-
Namhoon Lee, Kris M. Kitani
-
WACV 2016 (Oral)
- -
- -
- -
- © 2026 LOG. All rights reserved. - -
- -
- - - - - - - - - diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..77b9e47 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,4 @@ +# tools/build.py — install with: pip install -r requirements.txt +Jinja2>=3.1,<4 +Markdown>=3.5,<4 +PyYAML>=6.0,<7 diff --git a/research.html b/research.html deleted file mode 100644 index d91af86..0000000 --- a/research.html +++ /dev/null @@ -1,702 +0,0 @@ - - - - - - - Research | Lee Optimization Group - - - - - - -
- -
- Lee Optimization Group - - -
- -
-

Research Focus

-
- Model Compression -
-

- Model Compression - As AI models become larger and more computationally demanding, compression is essential for making them practical and sustainable. - We develop optimization methods that reduce training and inference costs while preserving model performance. - Our work focuses on extreme compression across the AI model lifecycle. - -

-
-
- Extreme Sparsity (EMNLP '26, ICLR '26, ICML '25 Spotlight, EMNLP '24) - We push the limits of LLM sparsity through advanced optimization frameworks such as ADMM, aiming for robust generalization even at extreme compression levels. -
-
- Joint Compression - We develop unified frameworks that integrate pruning, quantization, and distillation to reduce error accumulation and preserve core model capabilities, including reasoning, in highly compressed states. -
-
-
-
- -
- Distributed Training -
-

- Distributed Training - The shift toward massive-scale models has made distributed training essential for workloads that no single machine can support. - From an optimization perspective, the key challenge is maintaining efficiency while managing the overhead of communication between devices. - Our research develops robust algorithms for decentralized and high-latency training environments. - -

-
-
- Communication-Efficient Training (NeurIPS '26 WS) - We design methods that reduce the heavy data-sharing requirements of distributed training through information compression, reduced synchronization, and zeroth-order optimization. -
-
- Asynchronous Training (NeurIPS '26, ICML '26) - We create algorithms that allow devices to work independently and reduce waiting time, with a focus on correcting errors caused by slightly outdated information. -
-
- Device-Heterogeneous Training - We develop adaptive strategies for imbalanced computational resources across devices, aiming for consistent global convergence despite variations in hardware performance. -
-
-
-
- - - - -
- Advanced Optimization -
-

- Advanced Optimization - Optimization has long been a source of crucial ideas that drastically enhance all corners of deep neural network training, and many of its most impactful questions remain open. - Our research investigates these questions and develops optimization principles and algorithms for modern deep learning systems. - -

-
-
- Optimization for Generalization (UAI '25, ICML '25) - We study the role of loss landscape curvature in generalization and develop flatness-oriented optimization strategies with both theoretical advantages and scalability for modern deep neural networks. -
-
- Zeroth-Order Optimization for Black-Box Model Training (ICLR '25) - We develop methods that operate without direct gradient access, motivated by settings such as proprietary model APIs and decentralized learning systems where gradients are unavailable. -
-
-
-
- -
- -
- © 2026 LOG. All rights reserved. - -
- -
- - - - - - - - - diff --git a/CNAME b/static/CNAME similarity index 100% rename from CNAME rename to static/CNAME diff --git a/index_v1.html b/static/index_v1.html similarity index 100% rename from index_v1.html rename to static/index_v1.html diff --git a/lab_photo/jeju.jpg b/static/lab_photo/jeju.jpg similarity index 100% rename from lab_photo/jeju.jpg rename to static/lab_photo/jeju.jpg diff --git a/lab_photo/lab_photo.jpg b/static/lab_photo/lab_photo.jpg similarity index 100% rename from lab_photo/lab_photo.jpg rename to static/lab_photo/lab_photo.jpg diff --git a/lab_photo/yacht_tour.jpg b/static/lab_photo/yacht_tour.jpg similarity index 100% rename from lab_photo/yacht_tour.jpg rename to static/lab_photo/yacht_tour.jpg diff --git a/logo/anu_logo.png b/static/logo/anu_logo.png similarity index 100% rename from logo/anu_logo.png rename to static/logo/anu_logo.png diff --git a/logo/cmu.logo.png b/static/logo/cmu.logo.png similarity index 100% rename from logo/cmu.logo.png rename to static/logo/cmu.logo.png diff --git a/logo/cs_logo.png b/static/logo/cs_logo.png similarity index 100% rename from logo/cs_logo.png rename to static/logo/cs_logo.png diff --git a/logo/epfl_logo.png b/static/logo/epfl_logo.png similarity index 100% rename from logo/epfl_logo.png rename to static/logo/epfl_logo.png diff --git a/logo/etri_logo.png b/static/logo/etri_logo.png similarity index 100% rename from logo/etri_logo.png rename to static/logo/etri_logo.png diff --git a/logo/etri_mark.png b/static/logo/etri_mark.png similarity index 100% rename from logo/etri_mark.png rename to static/logo/etri_mark.png diff --git a/logo/google_logo.svg b/static/logo/google_logo.svg similarity index 100% rename from logo/google_logo.svg rename to static/logo/google_logo.svg diff --git a/logo/gsai_logo.png b/static/logo/gsai_logo.png similarity index 100% rename from logo/gsai_logo.png rename to static/logo/gsai_logo.png diff --git a/logo/iitp_logo.png b/static/logo/iitp_logo.png similarity index 100% rename from logo/iitp_logo.png rename to static/logo/iitp_logo.png diff --git a/logo/iitp_logo.svg b/static/logo/iitp_logo.svg similarity index 100% rename from logo/iitp_logo.svg rename to static/logo/iitp_logo.svg diff --git a/logo/iitp_mark.png b/static/logo/iitp_mark.png similarity index 100% rename from logo/iitp_mark.png rename to static/logo/iitp_mark.png diff --git a/logo/intel_logo.svg b/static/logo/intel_logo.svg similarity index 100% rename from logo/intel_logo.svg rename to static/logo/intel_logo.svg diff --git a/logo/ista_logo.png b/static/logo/ista_logo.png similarity index 100% rename from logo/ista_logo.png rename to static/logo/ista_logo.png diff --git a/logo/log_icon.png b/static/logo/log_icon.png similarity index 100% rename from logo/log_icon.png rename to static/logo/log_icon.png diff --git a/logo/log_logo.png b/static/logo/log_logo.png similarity index 100% rename from logo/log_logo.png rename to static/logo/log_logo.png diff --git a/logo/nairl_logo.png b/static/logo/nairl_logo.png similarity index 100% rename from logo/nairl_logo.png rename to static/logo/nairl_logo.png diff --git a/logo/naver_logo.svg b/static/logo/naver_logo.svg similarity index 100% rename from logo/naver_logo.svg rename to static/logo/naver_logo.svg diff --git a/logo/nrf_logo.jpg b/static/logo/nrf_logo.jpg similarity index 100% rename from logo/nrf_logo.jpg rename to static/logo/nrf_logo.jpg diff --git a/logo/nrf_logo.svg b/static/logo/nrf_logo.svg similarity index 100% rename from logo/nrf_logo.svg rename to static/logo/nrf_logo.svg diff --git a/logo/nrf_mark.png b/static/logo/nrf_mark.png similarity index 100% rename from logo/nrf_mark.png rename to static/logo/nrf_mark.png diff --git a/logo/oxford_logo.png b/static/logo/oxford_logo.png similarity index 100% rename from logo/oxford_logo.png rename to static/logo/oxford_logo.png diff --git a/logo/postech_logo.png b/static/logo/postech_logo.png similarity index 100% rename from logo/postech_logo.png rename to static/logo/postech_logo.png diff --git a/logo/postech_logo2.jpg b/static/logo/postech_logo2.jpg similarity index 100% rename from logo/postech_logo2.jpg rename to static/logo/postech_logo2.jpg diff --git a/logo/samsung_logo.svg b/static/logo/samsung_logo.svg similarity index 100% rename from logo/samsung_logo.svg rename to static/logo/samsung_logo.svg diff --git a/logo/snu_logo.png b/static/logo/snu_logo.png similarity index 100% rename from logo/snu_logo.png rename to static/logo/snu_logo.png diff --git a/logo/yonsei_logo.png b/static/logo/yonsei_logo.png similarity index 100% rename from logo/yonsei_logo.png rename to static/logo/yonsei_logo.png diff --git a/research_focus/opt-applications.png b/static/research_focus/opt-applications.png similarity index 100% rename from research_focus/opt-applications.png rename to static/research_focus/opt-applications.png diff --git a/research_focus/opt-applied_old.png b/static/research_focus/opt-applied_old.png similarity index 100% rename from research_focus/opt-applied_old.png rename to static/research_focus/opt-applied_old.png diff --git a/research_focus/opt-compression.png b/static/research_focus/opt-compression.png similarity index 100% rename from research_focus/opt-compression.png rename to static/research_focus/opt-compression.png diff --git a/research_focus/opt-compression_old.svg b/static/research_focus/opt-compression_old.svg similarity index 100% rename from research_focus/opt-compression_old.svg rename to static/research_focus/opt-compression_old.svg diff --git a/research_focus/opt-deep.png b/static/research_focus/opt-deep.png similarity index 100% rename from research_focus/opt-deep.png rename to static/research_focus/opt-deep.png diff --git a/research_focus/opt-deep_old.jpeg b/static/research_focus/opt-deep_old.jpeg similarity index 100% rename from research_focus/opt-deep_old.jpeg rename to static/research_focus/opt-deep_old.jpeg diff --git a/research_focus/opt-dist_old.png b/static/research_focus/opt-dist_old.png similarity index 100% rename from research_focus/opt-dist_old.png rename to static/research_focus/opt-dist_old.png diff --git a/research_focus/opt-distributed.png b/static/research_focus/opt-distributed.png similarity index 100% rename from research_focus/opt-distributed.png rename to static/research_focus/opt-distributed.png diff --git a/research_focus/opt-model.png b/static/research_focus/opt-model.png similarity index 100% rename from research_focus/opt-model.png rename to static/research_focus/opt-model.png diff --git a/stories/thumbs/23_boardgame.jpg b/stories/thumbs/23_boardgame.jpg deleted file mode 100644 index 4a5245d..0000000 Binary files a/stories/thumbs/23_boardgame.jpg and /dev/null differ diff --git a/stories/thumbs/23_cake.jpg b/stories/thumbs/23_cake.jpg deleted file mode 100644 index 2fd50fa..0000000 Binary files a/stories/thumbs/23_cake.jpg and /dev/null differ diff --git a/stories/thumbs/23_christmasparty.jpg b/stories/thumbs/23_christmasparty.jpg deleted file mode 100644 index 855d22a..0000000 Binary files a/stories/thumbs/23_christmasparty.jpg and /dev/null differ diff --git a/stories/thumbs/23_halonabeachcove.jpg b/stories/thumbs/23_halonabeachcove.jpg deleted file mode 100644 index 8e8f16b..0000000 Binary files a/stories/thumbs/23_halonabeachcove.jpg and /dev/null differ diff --git a/stories/thumbs/23_icml.jpg b/stories/thumbs/23_icml.jpg deleted file mode 100644 index f5731d7..0000000 Binary files a/stories/thumbs/23_icml.jpg and /dev/null differ diff --git a/stories/thumbs/23_icml2.jpg b/stories/thumbs/23_icml2.jpg deleted file mode 100644 index 1e0c266..0000000 Binary files a/stories/thumbs/23_icml2.jpg and /dev/null differ diff --git a/stories/thumbs/23_icml3.jpg b/stories/thumbs/23_icml3.jpg deleted file mode 100644 index 9d1ebde..0000000 Binary files a/stories/thumbs/23_icml3.jpg and /dev/null differ diff --git a/stories/thumbs/23_jkaia.jpg b/stories/thumbs/23_jkaia.jpg deleted file mode 100644 index 28d512b..0000000 Binary files a/stories/thumbs/23_jkaia.jpg and /dev/null differ diff --git a/stories/thumbs/23_logday.jpg b/stories/thumbs/23_logday.jpg deleted file mode 100644 index 6f835d1..0000000 Binary files a/stories/thumbs/23_logday.jpg and /dev/null differ diff --git a/stories/thumbs/23_movienight.jpg b/stories/thumbs/23_movienight.jpg deleted file mode 100644 index 9ca201a..0000000 Binary files a/stories/thumbs/23_movienight.jpg and /dev/null differ diff --git a/stories/thumbs/23_noscreenday.jpg b/stories/thumbs/23_noscreenday.jpg deleted file mode 100644 index 8f33e14..0000000 Binary files a/stories/thumbs/23_noscreenday.jpg and /dev/null differ diff --git a/stories/thumbs/23_postech.jpg b/stories/thumbs/23_postech.jpg deleted file mode 100644 index c2d7568..0000000 Binary files a/stories/thumbs/23_postech.jpg and /dev/null differ diff --git a/stories/thumbs/23_santa.jpg b/stories/thumbs/23_santa.jpg deleted file mode 100644 index 10715f4..0000000 Binary files a/stories/thumbs/23_santa.jpg and /dev/null differ diff --git a/stories/thumbs/23_teachersday.jpg b/stories/thumbs/23_teachersday.jpg deleted file mode 100644 index 55c7828..0000000 Binary files a/stories/thumbs/23_teachersday.jpg and /dev/null differ diff --git a/stories/thumbs/24_effl-log.jpg b/stories/thumbs/24_effl-log.jpg deleted file mode 100644 index a1a4930..0000000 Binary files a/stories/thumbs/24_effl-log.jpg and /dev/null differ diff --git a/stories/thumbs/24_logesports.jpg b/stories/thumbs/24_logesports.jpg deleted file mode 100644 index 5cfc66e..0000000 Binary files a/stories/thumbs/24_logesports.jpg and /dev/null differ diff --git a/stories/thumbs/24_logesports2.jpg b/stories/thumbs/24_logesports2.jpg deleted file mode 100644 index a671841..0000000 Binary files a/stories/thumbs/24_logesports2.jpg and /dev/null differ diff --git a/stories/thumbs/24_logesports3.jpg b/stories/thumbs/24_logesports3.jpg deleted file mode 100644 index fbcc21b..0000000 Binary files a/stories/thumbs/24_logesports3.jpg and /dev/null differ diff --git a/stories/thumbs/24_logspring.jpg b/stories/thumbs/24_logspring.jpg deleted file mode 100644 index 785aafb..0000000 Binary files a/stories/thumbs/24_logspring.jpg and /dev/null differ diff --git a/stories/thumbs/24_logspringouting.jpg b/stories/thumbs/24_logspringouting.jpg deleted file mode 100644 index 3761c5c..0000000 Binary files a/stories/thumbs/24_logspringouting.jpg and /dev/null differ diff --git a/stories/thumbs/25_boardgame.jpg b/stories/thumbs/25_boardgame.jpg deleted file mode 100644 index be7387e..0000000 Binary files a/stories/thumbs/25_boardgame.jpg and /dev/null differ diff --git a/stories/thumbs/25_iclr.jpg b/stories/thumbs/25_iclr.jpg deleted file mode 100644 index 3ef20e9..0000000 Binary files a/stories/thumbs/25_iclr.jpg and /dev/null differ diff --git a/stories/thumbs/25_icml.jpg b/stories/thumbs/25_icml.jpg deleted file mode 100644 index b077406..0000000 Binary files a/stories/thumbs/25_icml.jpg and /dev/null differ diff --git a/stories/thumbs/25_noscreenday.jpg b/stories/thumbs/25_noscreenday.jpg deleted file mode 100644 index c08324c..0000000 Binary files a/stories/thumbs/25_noscreenday.jpg and /dev/null differ diff --git a/stories/thumbs/25_openday.jpg b/stories/thumbs/25_openday.jpg deleted file mode 100644 index 4666c05..0000000 Binary files a/stories/thumbs/25_openday.jpg and /dev/null differ diff --git a/stories/thumbs/25_postech.jpg b/stories/thumbs/25_postech.jpg deleted file mode 100644 index 7e0c2ca..0000000 Binary files a/stories/thumbs/25_postech.jpg and /dev/null differ diff --git a/stories/thumbs/25_readinggroup.jpg b/stories/thumbs/25_readinggroup.jpg deleted file mode 100644 index 1a2f66c..0000000 Binary files a/stories/thumbs/25_readinggroup.jpg and /dev/null differ diff --git a/stories/thumbs/25_yearendparty.jpg b/stories/thumbs/25_yearendparty.jpg deleted file mode 100644 index 849b055..0000000 Binary files a/stories/thumbs/25_yearendparty.jpg and /dev/null differ diff --git a/stories/thumbs/26_acl.jpg b/stories/thumbs/26_acl.jpg deleted file mode 100644 index d20880b..0000000 Binary files a/stories/thumbs/26_acl.jpg and /dev/null differ diff --git a/stories/thumbs/26_aiconnect.jpg b/stories/thumbs/26_aiconnect.jpg deleted file mode 100644 index 22ed80d..0000000 Binary files a/stories/thumbs/26_aiconnect.jpg and /dev/null differ diff --git a/stories/thumbs/26_bowling.jpg b/stories/thumbs/26_bowling.jpg deleted file mode 100644 index 01ca090..0000000 Binary files a/stories/thumbs/26_bowling.jpg and /dev/null differ diff --git a/stories/thumbs/26_ckaia.jpg b/stories/thumbs/26_ckaia.jpg deleted file mode 100644 index 1a6bd55..0000000 Binary files a/stories/thumbs/26_ckaia.jpg and /dev/null differ diff --git a/stories/thumbs/26_das_dinner.jpg b/stories/thumbs/26_das_dinner.jpg deleted file mode 100644 index 0a7febe..0000000 Binary files a/stories/thumbs/26_das_dinner.jpg and /dev/null differ diff --git a/stories/thumbs/26_iclr.jpg b/stories/thumbs/26_iclr.jpg deleted file mode 100644 index 7d54f80..0000000 Binary files a/stories/thumbs/26_iclr.jpg and /dev/null differ diff --git a/stories/thumbs/26_iclrdinner.jpg b/stories/thumbs/26_iclrdinner.jpg deleted file mode 100644 index cd4f3b4..0000000 Binary files a/stories/thumbs/26_iclrdinner.jpg and /dev/null differ diff --git a/stories/thumbs/26_icml.jpg b/stories/thumbs/26_icml.jpg deleted file mode 100644 index 0b0276a..0000000 Binary files a/stories/thumbs/26_icml.jpg and /dev/null differ diff --git a/stories/thumbs/26_icml_poster.jpg b/stories/thumbs/26_icml_poster.jpg deleted file mode 100644 index 8162ed0..0000000 Binary files a/stories/thumbs/26_icml_poster.jpg and /dev/null differ diff --git a/stories/thumbs/26_kcc_cliff.jpg b/stories/thumbs/26_kcc_cliff.jpg deleted file mode 100644 index 035d1d0..0000000 Binary files a/stories/thumbs/26_kcc_cliff.jpg and /dev/null differ diff --git a/stories/thumbs/26_kcc_conference.jpg b/stories/thumbs/26_kcc_conference.jpg deleted file mode 100644 index d64de6d..0000000 Binary files a/stories/thumbs/26_kcc_conference.jpg and /dev/null differ diff --git a/stories/thumbs/26_kcc_yaut.jpg b/stories/thumbs/26_kcc_yaut.jpg deleted file mode 100644 index 1ee6d75..0000000 Binary files a/stories/thumbs/26_kcc_yaut.jpg and /dev/null differ diff --git a/stories/thumbs/26_logdinner.jpg b/stories/thumbs/26_logdinner.jpg deleted file mode 100644 index ad8a5f5..0000000 Binary files a/stories/thumbs/26_logdinner.jpg and /dev/null differ diff --git a/stories/thumbs/26_lovelog.jpg b/stories/thumbs/26_lovelog.jpg deleted file mode 100644 index c87318b..0000000 Binary files a/stories/thumbs/26_lovelog.jpg and /dev/null differ diff --git a/stories/thumbs/26_pl_workshop.jpg b/stories/thumbs/26_pl_workshop.jpg deleted file mode 100644 index 7686268..0000000 Binary files a/stories/thumbs/26_pl_workshop.jpg and /dev/null differ diff --git a/stories/thumbs/26_pl_workshop2.jpg b/stories/thumbs/26_pl_workshop2.jpg deleted file mode 100644 index 3afcbf5..0000000 Binary files a/stories/thumbs/26_pl_workshop2.jpg and /dev/null differ diff --git a/stories/thumbs/26_post-icml.jpg b/stories/thumbs/26_post-icml.jpg deleted file mode 100644 index c8a2839..0000000 Binary files a/stories/thumbs/26_post-icml.jpg and /dev/null differ diff --git a/tools/build.py b/tools/build.py new file mode 100644 index 0000000..9e9334a --- /dev/null +++ b/tools/build.py @@ -0,0 +1,683 @@ +#!/usr/bin/env python3 +"""Build the site: read data/, render layout/, write _site/. + + tools/build.py build into _site/ + tools/build.py --check validate data/ only, write nothing (exit 1 on errors) + tools/build.py --serve build, then serve _site/ on http://localhost:8000 + +Content lives in data// — one directory per kind of content, each with +its entries, a config.toml for presentation settings, and a README.md on how to +add an entry. Pages are Jinja templates in layout/; the text of pages that are +not built from data (research, hiring, contact) is plain HTML in content/. Files in static/ are +copied to the site root unchanged. + +Images are copied byte-for-byte. The only generated images are gallery +thumbnails, made from the photo itself with ImageMagick (see data/gallery). +""" + +import argparse +import datetime as dt +import functools +import http.server +import re +import shutil +import subprocess +import sys +import tomllib +from pathlib import Path + +import markdown +import yaml +from jinja2 import Environment, FileSystemLoader, StrictUndefined +from markupsafe import Markup, escape + +ROOT = Path(__file__).resolve().parent.parent +DATA = ROOT / "data" +LAYOUT_DIR = ROOT / "layout" +CONTENT = ROOT / "content" +STATIC = ROOT / "static" +OUT = ROOT / "_site" + +# Pages built from data/ by their own layout. Every content/.html is a +# page too, wrapped by layout/page.html. +PAGES = ["index", "news", "publications", "group", "gallery", "photo"] + +# Where each database's files are published. These paths are part of the site's +# public URLs (photo.html?image=stories/...), so they stay as they always were. +PUBLISH = { + DATA / "gallery" / "photos": "stories", + DATA / "members" / "photos": "group_profile", +} +THUMBS = "stories/thumbs" + + +class DataError(Exception): + pass + + +errors = [] + + +def fail(msg): + errors.append(msg) + + +# ------------------------------------------------------------------ helpers + +def load_config(db): + path = DATA / db / "config.toml" + with path.open("rb") as f: + return tomllib.load(f) + + +class _UniqueKeyLoader(yaml.SafeLoader): + """yaml.safe_load, except a key given twice in one mapping is an error + (plain YAML silently keeps the last one, which would drop an entry).""" + + def construct_mapping(self, node, deep=False): + seen = set() + for key_node, _ in node.value: + key = self.construct_object(key_node, deep=deep) + if key in seen: + raise DataError("line %d: %r is given twice" % (key_node.start_mark.line + 1, key)) + seen.add(key) + return super().construct_mapping(node, deep=deep) + + +def load_yaml(path): + with path.open(encoding="utf-8") as f: + return yaml.load(f, Loader=_UniqueKeyLoader) + + +_md = markdown.Markdown() +_EXTERNAL = re.compile(r'') + + +def render_inline(text): + """One paragraph of markdown -> inline HTML (no wrapping

). + + External links open in a new tab with rel="noopener", as everywhere on the + site; relative links (news.html, ...) stay in the same tab. + """ + html = _md.reset().convert(text.strip()) + if html.startswith("

") and html.endswith("

"): + html = html[3:-4] + html = _EXTERNAL.sub(r'
', html) + return Markup(html) + + +_SPOT = re.compile(r"\*\*(.+?)\*\*") + + +def render_venue(text): + """Venue line: plain text, with **...** marking a highlight (Oral, awards).""" + return Markup(_SPOT.sub(r'\1', str(escape(text)))) + + +def external(url): + return bool(re.match(r"https?://", url or "")) + + +# ------------------------------------------------------------------- people + +TAG = re.compile(r"(? person, from data/members/members.yaml (members, then alumni). + + A person can be in both lists (an MS graduate continuing as a PhD student) + under one tag; the current-member entry wins. Only current members have a + face: alumni tags show the name alone. + """ + + def __init__(self, raw): + self.by_tag = {} + for group, current in (("members", True), ("alumni", False)): + for p in raw.get(group, []) or []: + tag = p.get("tag", "") + if not re.fullmatch(r"[a-z0-9_]+", tag or ""): + fail("members.yaml: %s needs a tag of lowercase letters/digits (e.g. hyunjijung)" % p.get("name", p)) + continue + if tag in self.by_tag: + other = self.by_tag[tag] + if not (other["current"] and not current and other["name"] == p.get("name")): + fail("members.yaml: tag @%s is used for both %s and %s" % (tag, other["name"], p.get("name"))) + continue + name = p.get("name", "") + self.by_tag[tag] = { + "tag": tag, + "name": name, + "short": p.get("short") or name.split(" ")[0], + "url": p.get("url"), + "photo": "group_profile/" + p["photo"] if current and p.get("photo") else None, + "slug": Path(p.get("photo", "")).stem, + "current": current, + } + + def get(self, tag, where): + person = self.by_tag.get(tag) + if person is None: + fail("%s: @%s is not a tag in members/members.yaml" % (where, tag)) + return person + + def resolve(self, text, where, form): + """Replace each @tag with the person's name; return (text, people in order).""" + found = [] + + def sub(m): + person = self.get(m.group(1), where) + if person is None: + return m.group(0) + if person not in found: + found.append(person) + return person[form] + + return TAG.sub(sub, text), found + + +def _profile_link(person): + """Where a face links: the member's own site, else their card on group.html.""" + if person["url"] and external(person["url"]): + return Markup(' href="%s" target="_blank" rel="noopener"') % person["url"] + if person["url"]: + return Markup(' href="%s"') % person["url"] + return Markup(' href="group.html#%s"') % person["slug"] + + +def _face_img(person): + return Markup('') % person["photo"] + + +def news_faces(people, max_faces): + """Faces under a news item's date: a few, then "+N" (like Slack).""" + people = [p for p in people if p["current"]] + if not people: + return Markup("") + names = ", ".join(p["name"] for p in people) + shown = people[: max_faces - 1] if len(people) > max_faces else people + html = Markup('') % (names, names) + for p in shown: + html += Markup('%s') % (p["name"], _profile_link(p), _face_img(p)) + if len(shown) < len(people): + html += Markup('+%d') % (len(people) - len(shown)) + return html + Markup("") + + +def author_list(field, people, where): + """BibTeX author field -> (HTML author list, current members in order). + + "@hyunjijung* and Ziyang Wu" -> a face and name for the member (marks such + as * kept), plain text for everyone else. + """ + out, members = [], [] + for raw in re.split(r"\s+and\s+", field.strip()): + raw = raw.strip() + m = re.fullmatch(r"@([a-z0-9_]+)([*†‡]*)", raw) + person = people.get(m.group(1), where) if m else None + if person is None: + out.append(escape(raw)) + continue + label = person["name"] + m.group(2) + if not person["current"]: + out.append(escape(label)) + continue + members.append(person) + out.append(Markup('%s%s') + % (person["name"], _profile_link(person), _face_img(person), label)) + return Markup(", ").join(out), members + + +# --------------------------------------------------------------------- news + +NEWS_KEYS = {"date", "paper", "text"} +NEWS_DATE = re.compile(r"^(\d{4})-(\d{2})(?:-(\d{2}))?$") +# A Markdown link to a paper by its papers.bib key: [paper](@shin23closer). +PAPER_LINK = re.compile(r"\]\(@([a-z][a-z0-9_]*)\)") +# A link target that is a bare word is a key missing its @. +BARE_LINK = re.compile(r"\]\(([a-z][a-z0-9_]*)\)") + + +def load_news(papers_by_key, people): + """News items from data/news/.yaml, ordered by idx (higher = newer).""" + cfg = load_config("news") + entries, seen = [], {} + for path in sorted((DATA / "news").glob("*.yaml")): + if not re.fullmatch(r"\d{4}", path.stem): + fail("news/%s: news files are named by year, like 2026.yaml" % path.name) + continue + try: + items = load_yaml(path) or {} + except DataError as e: + fail("news/%s %s" % (path.name, e)) + continue + if not isinstance(items, dict): + fail("news/%s: items are keyed by idx (39:, 38:, ...)" % path.name) + continue + for idx, item in items.items(): + if not isinstance(idx, int): + fail("news/%s: %r is not an idx; each item starts with its number, like 39:" % (path.name, idx)) + continue + where = "news/%s idx %d" % (path.name, idx) + if not isinstance(item, dict): + fail("%s: needs date and text" % where) + continue + unknown = set(item) - NEWS_KEYS + if unknown: + fail("%s: unknown field(s) %s (allowed: %s)" % (where, ", ".join(sorted(unknown)), ", ".join(sorted(NEWS_KEYS)))) + if idx in seen: + fail("%s: idx %d is already used in %s" % (where, idx, seen[idx])) + seen[idx] = path.name + raw_date = item.get("date") + m = NEWS_DATE.match(str(raw_date or "")) + if not m: + fail('%s: date must be "YYYY-MM" or "YYYY-MM-DD", not %r' % (where, raw_date)) + continue + try: + date = dt.date(int(m.group(1)), int(m.group(2)), int(m.group(3) or 1)) + except ValueError: + fail("%s: %r is not a real date" % (where, raw_date)) + continue + if m.group(1) != path.stem: + fail("%s: dated %s but filed in %s" % (where, raw_date, path.name)) + body = " ".join(str(item.get("text") or "").split()) + if not body: + fail("%s: text is empty" % where) + continue + front = item + # Papers: named in front matter (paper: key), or linked in the text as + # [paper](key), which links to the paper's own `paper` URL. + keys = [] + listed = front.get("paper") or [] + for k in [listed] if isinstance(listed, str) else listed: + if not str(k).startswith("@"): + fail('%s: paper keys start with @, in quotes: paper: "@%s"' % (where, k)) + keys.append(str(k).lstrip("@")) + for m in BARE_LINK.finditer(body): + fail("%s: link target %r: write a paper key as (@%s)" % (where, m.group(1), m.group(1))) + + def link_paper(m): + key = m.group(1) + paper = papers_by_key.get(key) + if paper is None: + fail("%s: link target %r is not a key in publications/papers.bib" % (where, key)) + return m.group(0) + if not paper["url"]: + fail("%s: %s has no `paper` link in papers.bib to link to" % (where, key)) + return m.group(0) + if key not in keys: + keys.append(key) + return "](%s)" % paper["url"] + + body = PAPER_LINK.sub(link_paper, body) + + # Faces: the in-lab authors of those papers, then the members @tagged in + # the text, in that order. + faces = [] + for key in keys: + paper = papers_by_key.get(str(key)) + if paper is None: + fail("%s: paper %r is not a key in publications/papers.bib" % (where, key)) + continue + faces += [p for p in paper["members"] if p not in faces] + body, tagged = people.resolve(body, where, "short") + faces += [p for p in tagged if p not in faces] + entries.append({ + "idx": idx, + "date": date, + "date_label": date.strftime(cfg["date_format"]), + "html": render_inline(body), + "faces": news_faces(faces, cfg["faces"]["max"]), + "file": path.name, + }) + entries.sort(key=lambda e: e["idx"], reverse=True) + + since = cfg["page"].get("since") + if since: + y, mo = (int(x) for x in since.split("-")) + listed = [e for e in entries if e["date"] >= dt.date(y, mo, 1)] + else: + listed = entries + return {"config": cfg, "all": entries, "listed": listed, "home": listed[: cfg["home"]["count"]]} + + +# ------------------------------------------------------------- publications + +BIB_ENTRY = re.compile(r"@(\w+)\s*\{\s*([^,\s]+)\s*,") +BIB_FIELD = re.compile(r"\s*([A-Za-z][\w-]*)\s*=\s*") + + +def parse_bibtex(text, where): + """Entries of a .bib file, in file order: [(key, {field: value})]. + + Covers what papers.bib uses: @type{key, field = {value} or "value" or 2026}. + Field order and name case are kept (link fields are shown in that order, with + that label). Braces only protect capitalisation in BibTeX, so they are + dropped; \\& is &. Anything outside an entry is a comment, and so is a + line starting with % inside one. + """ + entries, pos = [], 0 + text = re.sub(r"(?m)^[ \t]*%.*$", "", text) + while True: + m = BIB_ENTRY.search(text, pos) + if not m: + return entries + key, fields, pos = m.group(2), {}, m.end() + if m.group(1).lower() in ("comment", "string", "preamble"): + continue + while True: + close = re.match(r"\s*\}", text[pos:]) + if close: + pos += close.end() + break + f = BIB_FIELD.match(text, pos) + if not f: + raise DataError("%s: entry %s: cannot read field near %r" % (where, key, text[pos:pos + 40])) + name, pos = f.group(1), f.end() + if text[pos] in "{\"": + opener, depth, start = text[pos], 0, pos + while True: + c = text[pos] + if opener == '"' and c == '"' and pos > start: + break + if c == "{": + depth += 1 + elif c == "}": + depth -= 1 + if depth == 0 and opener == "{": + break + pos += 1 + if pos >= len(text): + raise DataError("%s: entry %s: unclosed value of %s" % (where, key, name)) + value, pos = text[start + 1:pos], pos + 1 + else: + v = re.match(r"[^,}\s]+", text[pos:]) + value, pos = v.group(0), pos + v.end() + if name.lower() in (n.lower() for n in fields): + raise DataError("%s: entry %s: %s is given twice" % (where, key, name)) + fields[name] = " ".join(value.replace("{", "").replace("}", "").replace("\\&", "&").split()) + comma = re.match(r"\s*,", text[pos:]) + if comma: + pos += comma.end() + entries.append((key, fields)) + + +# Fields of a papers.bib entry. Any other field is a link: its name is the label +# shown on the page ([paper], [code], ...), its value the URL. +PAPER_TEXT = {"title", "author", "venue", "year", "also", "award"} +PAPER_FLAGS = {"is_preprint", "is_previous", "oral", "spotlight", "spaced_links"} + + +def flag(value, where, name): + v = value.strip().lower() + if v in ("true", "yes", "1"): + return True + if v in ("false", "no", "0", ""): + return False + fail("%s: %s must be true or false, not %r" % (where, name, value)) + return False + + +def venue_line(p): + """venue (Oral); also; award — the award highlighted, as on the page.""" + line = str(escape(p["venue"])) + if p["oral"] or p["spotlight"]: + line += ' (%s)' % ("Oral" if p["oral"] else "Spotlight") + if p.get("also"): + line += "; " + str(escape(p["also"])) + if p.get("award"): + line += '; %s' % escape(p["award"]) + return Markup(line) + + +def load_publications(people): + cfg = load_config("publications") + where = "publications/papers.bib" + try: + entries = parse_bibtex((DATA / "publications" / "papers.bib").read_text(encoding="utf-8"), where) + except DataError as e: + fail(str(e)) + entries = [] + + papers, keys = [], set() + for key, f in entries: + here = "%s: %s" % (where, key) + if key in keys: + fail("%s: the key %s is used twice" % (where, key)) + keys.add(key) + lower = {k.lower(): v for k, v in f.items()} + for req in ("title", "author", "venue", "year"): + if not lower.get(req): + fail("%s: missing %s" % (here, req)) + if lower.get("year") and not re.fullmatch(r"\d{4}", lower["year"]): + fail("%s: year must be four digits, not %r" % (here, lower["year"])) + p = {k: lower.get(k, "") for k in PAPER_TEXT} + for k in PAPER_FLAGS: + p[k] = flag(lower.get(k, ""), here, k) + if p["is_preprint"] and p["is_previous"]: + fail("%s: is_preprint and is_previous cannot both be true" % here) + if p["oral"] and p["spotlight"]: + fail("%s: oral and spotlight cannot both be true" % here) + links = [] + for name, url in f.items(): + if name.lower() in PAPER_TEXT | PAPER_FLAGS: + continue + if not re.match(r"(https?://|[\w./-]+$)", url): + fail("%s: %s is not a known field, so it is read as a link, but %r is not a URL" % (here, name, url)) + links.append((name, url)) + sep = " " if p["spaced_links"] else cfg["links"]["separator"] + authors, members = author_list(p["author"], people, here) + papers.append({ + "title": p["title"], + "authors": authors, + "members": members, + "url": next((u for n, u in links if n.lower() == "paper"), None), + "venue": venue_line(p), + "links": Markup(sep).join( + Markup('%s') % (url, label) + if external(url) else Markup('%s') % (url, label) + for label, url in links + ), + "key": key, + "group": "preprint" if p["is_preprint"] else "previous" if p["is_previous"] else p["year"], + }) + + # Preprints first, then years newest first, then earlier work. Within a group, + # papers keep their order in the file. + years = sorted({p["group"] for p in papers if p["group"] not in ("preprint", "previous")}, reverse=True) + order = ["preprint"] + years + ["previous"] + labels = cfg["groups"] + groups = [ + {"key": g, "label": labels.get(g, g), "papers": [p for p in papers if p["group"] == g]} + for g in order + ] + return {"config": cfg, "groups": [g for g in groups if g["papers"]], "by_key": {p["key"]: p for p in papers}} + + +# ------------------------------------------------------------------ members + +def load_members(): + cfg = load_config("members") + try: + data = load_yaml(DATA / "members" / "members.yaml") or {} + except DataError as e: + fail("members.yaml %s" % e) + data = {} + # Each section maps tag -> person, in display order; flatten to lists. + raw = {} + for group in ("members", "alumni"): + section = data.get(group) or {} + if not isinstance(section, dict): + fail("members.yaml: %s is keyed by tag (hyunjijung:, ...)" % group) + section = {} + raw[group] = [] + for tag, person in section.items(): + if not isinstance(person, dict): + fail("members.yaml: %s: %s needs name and the other fields below it" % (group, tag)) + continue + if "tag" in person: + fail("members.yaml: %s: the tag is the key, so remove the tag field" % tag) + raw[group].append(dict(person, tag=str(tag))) + photos = DATA / "members" / "photos" + members = [] + for m in raw.get("members", []): + for req in ("name", "photo", "position"): + if not m.get(req): + fail("members.yaml: %s is missing %s" % (m.get("name", m), req)) + if m.get("photo") and not (photos / m["photo"]).exists(): + fail("members.yaml: %s's photo %s is not in data/members/photos/" % (m.get("name"), m["photo"])) + members.append(dict(m, alt=m.get("alt", m.get("name")), url=m.get("url"))) + alumni = [dict(a, url=a.get("url"), note=a.get("note", "")) for a in raw.get("alumni", [])] + return {"config": cfg, "members": members, "alumni": alumni, "people": People(raw)} + + +# ------------------------------------------------------------------ gallery + +def load_gallery(): + cfg = load_config("gallery") + photos_dir = DATA / "gallery" / "photos" + photos, listed = [], set() + try: + items = load_yaml(DATA / "gallery" / "gallery.yaml") or {} + except DataError as e: + fail("gallery.yaml %s" % e) + items = {} + if not isinstance(items, dict): + fail("gallery.yaml: photos are keyed by idx (46:, 45:, ...)") + items = {} + # Highest idx first: newest photos lead the grid and the Members preview. + for k in items: + if not isinstance(k, int): + fail("gallery.yaml: %r is not an idx; each photo starts with its number, like 46:" % (k,)) + for i in sorted((k for k in items if isinstance(k, int)), reverse=True): + p = items[i] + if not isinstance(p, dict) or not p.get("file") or not p.get("caption"): + fail("gallery.yaml %d: needs file and caption" % i) + continue + unknown = set(p) - {"file", "caption", "alt"} + if unknown: + fail("gallery.yaml %d: unknown field(s) %s (allowed: alt, caption, file)" % (i, ", ".join(sorted(unknown)))) + name = p["file"] + if name in listed: + fail("gallery.yaml %d: %s is already listed under another number" % (i, name)) + if not (photos_dir / name).exists(): + fail("gallery.yaml: %s is not in data/gallery/photos/" % name) + listed.add(name) + photos.append({ + "file": name, + "caption": p["caption"], + "alt": p.get("alt", p["caption"]), + "full": "stories/" + name, + "thumb": "%s/%s" % (THUMBS, Path(name).with_suffix(".jpg").name), + }) + for path in photos_dir.iterdir(): + if path.is_file() and path.name not in listed and not path.name.startswith("."): + print("note: data/gallery/photos/%s is not in gallery.yaml (published, not shown)" % path.name) + return {"config": cfg, "photos": photos, "preview": photos[: cfg["preview"]["count"]]} + + +# ------------------------------------------------------------------- output + +def thumbnail(src, dest, cfg): + """Square center crop, same command the pre-commit hook used to run.""" + if dest.exists() and dest.stat().st_mtime >= src.stat().st_mtime: + return + tool = shutil.which("magick") or shutil.which("convert") + if not tool: + raise SystemExit("ImageMagick is required to make gallery thumbnails " + "(brew install imagemagick / apt install imagemagick)") + size = cfg["size"] + subprocess.run([ + tool, str(src), "-auto-orient", + "-resize", "%dx%d^" % (size, size), "-gravity", "center", "-extent", "%dx%d" % (size, size), + "-strip", "-interlace", "Plane", "-sampling-factor", "4:2:0", + "-quality", str(cfg["quality"]), str(dest), + ], check=True) + + +def copy_tree(src, dest): + for path in src.rglob("*"): + if path.is_file() and path.name != ".DS_Store": + target = dest / path.relative_to(src) + target.parent.mkdir(parents=True, exist_ok=True) + if not target.exists() or target.stat().st_mtime < path.stat().st_mtime: + shutil.copy2(path, target) + + +def build(check_only=False): + with (DATA / "site.toml").open("rb") as f: + site_cfg = tomllib.load(f) + members = load_members() + publications = load_publications(members["people"]) + site = { + "site": site_cfg, + "news": load_news(publications["by_key"], members["people"]), + "publications": publications, + "members": members, + "gallery": load_gallery(), + } + if errors: + print("Data errors:", file=sys.stderr) + for e in errors: + print(" - " + e, file=sys.stderr) + return 1 + if check_only: + print("data/ is valid.") + return 0 + + env = Environment( + loader=FileSystemLoader(LAYOUT_DIR), + autoescape=True, + undefined=StrictUndefined, + keep_trailing_newline=True, + trim_blocks=True, + lstrip_blocks=True, + ) + OUT.mkdir(exist_ok=True) + for page in PAGES: + html = env.get_template(page + ".html").render(page=page, **site) + (OUT / (page + ".html")).write_text(html, encoding="utf-8") + + # content/.html: plain HTML for inside
, in the shared frame. + # The title is the page's nav label (or its file name). + labels = {item["page"]: item["label"] for item in site_cfg["nav"]} + content_pages = sorted(CONTENT.glob("*.html")) + for path in content_pages: + page = path.stem + if page in PAGES: + raise SystemExit("content/%s.html clashes with layout/%s.html" % (page, page)) + html = env.get_template("page.html").render( + page=page, + title=labels.get(page, page.capitalize()), + content=Markup(path.read_text(encoding="utf-8").strip("\n")), + **site, + ) + (OUT / (page + ".html")).write_text(html, encoding="utf-8") + + copy_tree(STATIC, OUT) + for src, dest in PUBLISH.items(): + copy_tree(src, OUT / dest) + thumbs = OUT / THUMBS + thumbs.mkdir(parents=True, exist_ok=True) + for p in site["gallery"]["photos"]: + thumbnail(DATA / "gallery" / "photos" / p["file"], OUT / p["thumb"], site["gallery"]["config"]["thumbnail"]) + print("Built %d pages into %s" % (len(PAGES) + len(content_pages), OUT.relative_to(ROOT))) + return 0 + + +def main(): + ap = argparse.ArgumentParser(description=__doc__.split("\n\n")[0]) + ap.add_argument("--check", action="store_true", help="validate data/ only") + ap.add_argument("--serve", action="store_true", help="serve _site/ after building") + ap.add_argument("--port", type=int, default=8000) + args = ap.parse_args() + status = build(check_only=args.check) + if status or not args.serve: + return status + handler = functools.partial(http.server.SimpleHTTPRequestHandler, directory=str(OUT)) + print("Serving http://localhost:%d (Ctrl-C to stop)" % args.port) + http.server.ThreadingHTTPServer(("", args.port), handler).serve_forever() + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/tools/optimize-images.sh b/tools/optimize-images.sh index 3cd7dbb..dbb46db 100755 --- a/tools/optimize-images.sh +++ b/tools/optimize-images.sh @@ -11,8 +11,8 @@ # multiple of its display size, re-encodes it, and strips EXIF (which also drops # GPS coordinates from lab photos). # -# Gallery thumbnails under stories/thumbs/ are generated automatically from the -# originals in stories/ — never create them by hand. +# Gallery thumbnails are not stored at all: tools/build.py makes them from the +# photos at build time (settings in data/gallery/config.toml). # # Every processed file is stamped with a marker in its image comment, so running # this repeatedly is safe: already-optimized files are skipped rather than @@ -52,26 +52,36 @@ fi # # Sizes are ~3x the CSS display size so the images stay sharp on retina screens # and when the layout grows: -# stories/ click-through "full size" view -> 2560px -# stories/thumbs/ gallery grid tiles, 112-180px square -> 600px square -# lab_photo/ home page hero, <=800px wide -> 1600px -# group_profile/ member avatars, 140px square -> 512px -# research_focus/ research cards, 165px wide -> 500px -# logo/ left alone; small already, and logos need to stay crisp +# data/gallery/photos/ click-through "full size" view -> 2560px +# static/lab_photo/ home page hero, <=800px wide -> 1600px +# data/members/photos/ member avatars, 140px square -> 512px +# static/research_focus/ research cards, ~124px square -> 500px +# static/logo/ left alone; small already, and logos need to stay crisp # --------------------------------------------------------------------------- +IMAGE_DIRS="data/gallery/photos static/lab_photo data/members/photos static/research_focus" + rule_for() { case "$1" in - stories/thumbs/*) echo "600 80 jpg square" ;; - stories/*) echo "2560 82 jpg fit" ;; - lab_photo/*) echo "1600 85 jpg fit" ;; - group_profile/*) echo "512 85 jpg fit" ;; - research_focus/*) echo "500 85 keep fit" ;; - *) echo "" ;; + data/gallery/photos/*) echo "2560 82 jpg fit" ;; + static/lab_photo/*) echo "1600 85 jpg fit" ;; + data/members/photos/*) echo "512 85 jpg fit" ;; + static/research_focus/*) echo "500 85 keep fit" ;; + *) echo "" ;; + esac +} + +# The file that refers to images in a directory, by file name. +references_for() { + case "$1" in + data/gallery/photos/*) echo "data/gallery/gallery.yaml" ;; + data/members/photos/*) echo "data/members/members.yaml" ;; + static/research_focus/*) echo "content/research.html" ;; + static/lab_photo/*) echo "layout/index.html" ;; esac } is_raster() { - case "${1,,}" in + case "$(printf '%s' "$1" | tr '[:upper:]' '[:lower:]')" in *.jpg|*.jpeg|*.png) return 0 ;; *) return 1 ;; esac @@ -165,80 +175,31 @@ optimize_one() { printf " %-46s %8s -> %8s\n" "$dst" "$(human "$before")" "$(human "$after")" } -# Generate stories/thumbs/.jpg for any original that has no thumbnail. -# -# With no arguments, scans every original in stories/. With arguments, only the -# given paths are considered, so an explicit run still produces thumbnails — -# passing files used to skip thumbnail generation entirely. -generate_thumbs() { - mkdir -p stories/thumbs - local made=0 - local orig base thumb - local sources=() - - if [ "$#" -gt 0 ]; then - for orig in "$@"; do - orig="${orig#./}" - # optimize_one may have renamed the file to .jpg. - [ -f "$orig" ] || orig="${orig%.*}.jpg" - sources+=("$orig") - done - else - sources=(stories/*) - fi - - for orig in ${sources[@]+"${sources[@]}"}; do - [ -f "$orig" ] || continue - # Only originals directly under stories/ get a gallery thumbnail. - case "$orig" in - stories/thumbs/*) continue ;; - stories/*) ;; - *) continue ;; - esac - is_raster "$orig" || continue - base="$(basename "${orig%.*}")" - thumb="stories/thumbs/${base}.jpg" - # A thumbnail under any extension counts as present. - if compgen -G "stories/thumbs/${base}."* >/dev/null; then - continue - fi - if [ "$CHECK_ONLY" = 1 ]; then - echo " would generate $thumb" - else - convert "$orig" -auto-orient -resize "600x600^" -gravity center \ - -extent 600x600 -strip -interlace Plane -sampling-factor 4:2:0 \ - -quality 80 -set comment "$MARKER" "$thumb" - chmod 644 "$thumb" - echo " generated thumbnail $thumb" - fi - made=$((made + 1)) - done - [ "$made" -gt 0 ] || return 0 -} - -# When a file changes extension, keep the HTML pointing at it. +# When a file changes extension (photo.png -> photo.jpg), update the data file +# that names it, so the build still finds it. rewrite_references() { - [ "${#RENAMES[@]}" -gt 0 ] || return 0 - local pair old new page + [ "${#RENAMES[@]}" -gt 0 ] 2>/dev/null || return 0 + local pair old new ref for pair in "${RENAMES[@]}"; do old="${pair%%=>*}" new="${pair#*=>}" - for page in *.html; do - [ -f "$page" ] || continue - if grep -qF "$old" "$page"; then - # Match the path inside an attribute so a shorter name cannot - # partially match a longer one. - python3 - "$page" "$old" "$new" <<'PY' -import sys -page, old, new = sys.argv[1], sys.argv[2], sys.argv[3] -text = open(page, encoding="utf-8").read() -for quote in ('"', "'"): - text = text.replace(f"{quote}{old}{quote}", f"{quote}{new}{quote}") -open(page, "w", encoding="utf-8").write(text) + ref="$(references_for "$old")" + [ -n "$ref" ] && [ -f "$ref" ] || continue + python3 - "$ref" "$old" "$new" <<'PY' +import os, re, sys +ref, old, new = sys.argv[1], sys.argv[2], sys.argv[3] +text = open(ref, encoding="utf-8").read() +if ref.endswith(".html"): + # Templates refer to static files by their published path (lab_photo/x.jpg). + pairs = [(old.split("/", 1)[1], new.split("/", 1)[1])] +else: + pairs = [(os.path.basename(old), os.path.basename(new))] +for a, b in pairs: + # Whole values only, so a.png cannot match inside another name. + text = re.sub(r"(? $new" - fi - done + echo " updated reference in $ref: $(basename "$old") -> $(basename "$new")" done } @@ -248,13 +209,11 @@ if [ "${#TARGETS[@]}" -gt 0 ]; then for t in "${TARGETS[@]}"; do optimize_one "$t" done - generate_thumbs "${TARGETS[@]}" else - # Originals first, so generated thumbnails come from the optimized source. + # shellcheck disable=SC2086 # IMAGE_DIRS is a word list on purpose while IFS= read -r f; do optimize_one "$f" - done < <(find stories lab_photo group_profile research_focus -type f 2>/dev/null | LC_ALL=C sort) - generate_thumbs + done < <(find $IMAGE_DIRS -type f 2>/dev/null | LC_ALL=C sort) fi rewrite_references diff --git a/tools/sync-home-news.py b/tools/sync-home-news.py deleted file mode 100755 index a103f6a..0000000 --- a/tools/sync-home-news.py +++ /dev/null @@ -1,110 +0,0 @@ -#!/usr/bin/env python3 -"""Rewrite the Recent News list on index.html from news.html. - -news.html is the single source of truth for news. The home page shows only the -newest few items, and used to be edited by hand — which meant a link added on the -News page silently went missing on the home page. This keeps the two in step. - - tools/sync-home-news.py update index.html in place - tools/sync-home-news.py --check report drift only, change nothing (exit 1) - -Commented-out blocks further down the home page's news list are left untouched; -only the run of live items above them is regenerated. -""" - -import os -import re -import sys - -HOME = 'index.html' -ARCHIVE = 'news.html' -COUNT = 5 - -ITEM = re.compile( - r'
\s*' - r'([^<]+)\s*' - r'(.*?)\s*' - r'
', - re.S, -) - - -def newest_items(archive_html): - """The first COUNT news items in document order (news.html is newest-first).""" - out = [] - for m in ITEM.finditer(archive_html): - out.append((m.group(1).strip(), ' '.join(m.group(2).split()))) - if len(out) == COUNT: - break - return out - - -def render(items): - blocks = [] - for date, content in items: - blocks.append( - '
\n' - ' %s\n' - ' %s\n' - '
' % (date, content) - ) - return '\n\n'.join(blocks) - - -def news_list_span(home_html): - """(start, end) of the .news-list element's inner content.""" - open_tag = '
' - i = home_html.index(open_tag) + len(open_tag) - anchor = home_html.index('

') - j = home_html.rindex('

', i, anchor) - return i, j - - -def rebuild(home_html, items): - i, j = news_list_span(home_html) - inner = home_html[i:j] - - # Keep everything from the first comment onward (disabled entries). - cut = inner.find('