From 43c9f4cb4bfd70cddbc45f1ca808a37e599fc782 Mon Sep 17 00:00:00 2001 From: gspark Date: Thu, 22 Oct 2026 11:54:29 +0900 Subject: [PATCH] Rename the Python package npu_lora to npu_train; keep npu_lora as a deprecated alias (#46) - git mv npu_lora npu_train; imports, Helm job, k8s manifest, vendor script, docs and tests use npu_train. - pyproject: distribution npu-train, packages npu_train + npu_lora, CLI npu-train added next to npu-lora, airm workload npu-lora -> npu_train.airm:train. - npu_lora/__init__.py: DeprecationWarning plus a meta-path finder that maps npu_lora.X to the same module object as npu_train.X, so `import npu_lora.X` and `python -m npu_lora.X` keep working; files that exist only under npu_lora/ still resolve there; falls back to the sibling directory for editable installs made before the rename. - tests/test_compat.py covers these cases. Co-Authored-By: Claude Opus 5.5 --- .research/events.jsonl | 1 + README.ko.md | 28 ++++--- README.md | 30 +++---- STATUS.md | 20 +++-- ai-rm.yaml | 4 +- deploy/helm/npu-lora/templates/job.yaml | 2 +- deploy/helm/npu-lora/values.yaml | 2 +- docs/compiler-workarounds.md | 6 +- docs/validation.md | 6 +- npu_lora/__init__.py | 89 ++++++++++++++++++--- npu_train/__init__.py | 16 ++++ {npu_lora => npu_train}/airm.py | 2 +- {npu_lora => npu_train}/backends.py | 0 {npu_lora => npu_train}/bench.py | 2 +- {npu_lora => npu_train}/cli.py | 0 {npu_lora => npu_train}/compare.py | 2 +- {npu_lora => npu_train}/data.py | 0 {npu_lora => npu_train}/diagnose.py | 0 {npu_lora => npu_train}/export.py | 0 {npu_lora => npu_train}/graph.py | 0 {npu_lora => npu_train}/hwprobe.py | 2 +- {npu_lora => npu_train}/k8s.py | 6 +- {npu_lora => npu_train}/llama.py | 0 {npu_lora => npu_train}/metrics.py | 0 {npu_lora => npu_train}/moe.py | 0 {npu_lora => npu_train}/moe_device.py | 0 {npu_lora => npu_train}/optim_device.py | 2 +- {npu_lora => npu_train}/trainer.py | 0 pyproject.toml | 10 ++- scripts/vendor-into-lmd-top.sh | 6 +- tests/test_airm.py | 4 +- tests/test_compat.py | 101 ++++++++++++++++++++++++ tests/test_cpu.py | 36 ++++----- 33 files changed, 288 insertions(+), 89 deletions(-) create mode 100644 npu_train/__init__.py rename {npu_lora => npu_train}/airm.py (97%) rename {npu_lora => npu_train}/backends.py (100%) rename {npu_lora => npu_train}/bench.py (98%) rename {npu_lora => npu_train}/cli.py (100%) rename {npu_lora => npu_train}/compare.py (96%) rename {npu_lora => npu_train}/data.py (100%) rename {npu_lora => npu_train}/diagnose.py (100%) rename {npu_lora => npu_train}/export.py (100%) rename {npu_lora => npu_train}/graph.py (100%) rename {npu_lora => npu_train}/hwprobe.py (97%) rename {npu_lora => npu_train}/k8s.py (96%) rename {npu_lora => npu_train}/llama.py (100%) rename {npu_lora => npu_train}/metrics.py (100%) rename {npu_lora => npu_train}/moe.py (100%) rename {npu_lora => npu_train}/moe_device.py (100%) rename {npu_lora => npu_train}/optim_device.py (99%) rename {npu_lora => npu_train}/trainer.py (100%) create mode 100644 tests/test_compat.py diff --git a/.research/events.jsonl b/.research/events.jsonl index fd5e023..aed9877 100644 --- a/.research/events.jsonl +++ b/.research/events.jsonl @@ -13,3 +13,4 @@ {"id": "b958b783d2b041f2ba43e072eb284abc", "ts": "2026-10-21T21:57:00+09:00", "task": "#35 MoE hot experts batched", "result": "batched hot experts 13.0 s/step vs host-only 13.5 (alternating steps, Qwen3-30B-A3B, RNGD); per-expert main 42.4 vs 17.2", "next": "more rows per call (longer S), hot fraction 0.5, drop the recomputed forward", "state": "active", "done": false, "blocker": "", "metrics": {"step_time_s": "13.0"}, "params": {}, "artifacts": [], "experiments": ["experiments/2026-10-21-moe-batched-hot-experts.md"], "labels": ["experiment", "benchmark"], "review": false, "todos": [], "note": "", "agent": "claude", "host": "control-node", "branch": "perf/35-batch-hot-experts", "commit": "9d5e498"} {"id": "c37c232f20a045e0aa3315cc1c786106", "ts": "2026-10-21T22:27:58+09:00", "task": "#35 MoE hot experts batched", "result": "hot+keep_graph 5.19 s/step vs host-only+keep 8.00 (paired median -2.66 s, 6/6 rounds) and vs main host-only 9.21 s; Qwen3-30B-A3B S128 RNGD", "next": "", "state": "idle", "done": true, "blocker": "", "metrics": {"step_time_s": "5.19"}, "params": {}, "artifacts": [], "experiments": ["experiments/2026-10-21-moe-batched-hot-experts.md"], "labels": ["experiment", "benchmark"], "review": false, "todos": [], "note": "", "agent": "claude", "host": "control-node", "branch": "perf/35-hot-experts-faster", "commit": "1fee9f2-dirty"} {"id": "924cf1d262d54d208adcb1b5c47ceff5", "ts": "2026-10-22T01:48:36+09:00", "task": "#40 resident AdamW DRAM_SHARED OOM", "result": "2 RNGD x 8 resident 200/200 steps, no OOM, 4.43 s/step (463 tok/s) steady; 1 card x 4 resident at low load 4.79 s vs 1-card offload 5.11 s, model +3.0%", "next": "report the runtime flag to Furiosa", "state": "idle", "done": true, "blocker": "", "metrics": {"tok_s": "462.5", "step_s": "4.43"}, "params": {"cards": "2", "resident_layers": "8", "steps": "200", "host_load": "15-50", "sdk": "furiosa-torch-2026.2.0"}, "artifacts": [], "experiments": ["experiments/2026-10-22-resident-200-steps.md"], "labels": ["benchmark"], "review": false, "todos": [], "note": "", "agent": "claude", "host": "control-node", "branch": "exp/40-resident-200-steps", "commit": "b3cb03b-dirty"} +{"id": "f20511ff9efe4bae939ee2520a5274f6", "ts": "2026-10-22T11:54:14+09:00", "task": "#46 rename package npu_lora to npu_train", "result": "package moved to npu_train; npu_lora kept as deprecated alias (same module objects, python -m npu_lora.X works); CPU tests 38/38 in a clean venv", "next": "maintainer review of PR; then uninstall npu-lora and pip install -e . on shared hosts", "state": "active", "done": false, "blocker": "", "metrics": {}, "params": {}, "artifacts": [], "experiments": [], "labels": ["infra"], "review": true, "todos": [], "note": "", "agent": "claude", "host": "dev-host", "branch": "refactor/46-npu-train-package", "commit": "af1e8f5-dirty"} diff --git a/README.ko.md b/README.ko.md index bc2ad65..c00b5f1 100644 --- a/README.ko.md +++ b/README.ko.md @@ -30,7 +30,9 @@ --- -이전 저장소 이름은 `npu-lora`입니다. Python 패키지, CLI, Helm chart 이름은 `npu-lora` 그대로입니다. +이전 저장소 이름은 `npu-lora`입니다. Python 패키지는 `npu_train`(배포 이름 `npu-train`)입니다. 예전 패키지 이름 +`npu_lora`도 deprecated 별칭으로 그대로 import 하고 실행할 수 있습니다(`python -m npu_lora.`). CLI 명령 +`npu-lora`(`npu-train`으로도 설치됨), ai-rm workload 이름, Helm chart 이름은 `npu-lora` 그대로입니다. **npu-lora**는 FuriosaAI RNGD에서 Llama 계열 모델을 학습한다. RNGD의 SDK는 추론용 컴파일러와 런타임만 제공한다. npu-lora는 역전파를 일반 컴파일 그래프로 바꾸고, optimizer는 host에서 돌리며, vLLM과 llm-d가 그대로 @@ -119,7 +121,7 @@ pip install -e . apt-get install -y gcc-aarch64-linux-gnu libc6-dev-arm64-cross libc6-dev ``` -- **평가:** `vllm` (`npu_lora.bench`에만 필요). +- **평가:** `vllm` (`npu_train.bench`에만 필요). ```bash # 1. RNGD 기울기가 CPU 기준과 일치하는지 확인 (앞 2 레이어). @@ -174,9 +176,9 @@ Job은 공식 `furiosa-llm` 이미지에 ConfigMap으로 패키지를 마운트
```python -from npu_lora import LoRATrainer, LoRAConfig, TrainConfig -from npu_lora import backends, export -from npu_lora.data import PackedDataset +from npu_train import LoRATrainer, LoRAConfig, TrainConfig +from npu_train import backends, export +from npu_train.data import PackedDataset from transformers import AutoTokenizer trainer = LoRATrainer( @@ -209,9 +211,9 @@ export.save_peft(trainer, "./adapter") # TrainConfig(full=True)이면 expor | `npu-lora serve` | 실행 중인 vLLM에 어댑터 로드 | | `npu-lora eval` | OpenAI 호환 엔드포인트로 키워드 정답률 평가 | | `npu-lora job` | 쿠버네티스 학습 Job 출력 | -| `python -m npu_lora.bench` | 기본 모델 / 어댑터 / 전체 모델의 vLLM 오프라인 벤치마크 | -| `python -m npu_lora.compare` | 학습 loss 곡선 두 개 비교 | -| `python -m npu_lora.hwprobe` | host↔device 대역폭과 전송/계산 겹침 측정 | +| `python -m npu_train.bench` | 기본 모델 / 어댑터 / 전체 모델의 vLLM 오프라인 벤치마크 | +| `python -m npu_train.compare` | 학습 loss 곡선 두 개 비교 | +| `python -m npu_train.hwprobe` | host↔device 대역폭과 전송/계산 겹침 측정 | 자주 쓰는 학습 옵션: @@ -237,7 +239,7 @@ npu-lora는 ai-rm 구성요소다(계층 `l3-serving`, kind `workload`, 성숙 | Entry-point 그룹 | 이름 | 객체 | | --- | --- | --- | -| `airm.workloads` | `npu-lora` | `npu_lora.airm:train` | +| `airm.workloads` | `npu-lora` | `npu_train.airm:train` | ```python from rm_core import Workload, registry @@ -294,7 +296,7 @@ helm template deploy/helm/npu-lora --set train.model=… --set train.data=… rm-core `METRICS` 이름을 쓴다. 학습 처리량 `tok_s`(Prometheus `airm_tok_s`, 태그 `component=npu-lora`, `workload=train`, `model`, `vendor`, `run_id`; runner 반환값에도 들어 있다). 정확도 `accuracy_pct`는 -`npu_lora.bench` 결과 JSON의 `measurements`(태그 `dataset=nsmc|mmlu`, `run_id`=변형 이름)에 있다. rm-core에 없는 +`npu_train.bench` 결과 JSON의 `measurements`(태그 `dataset=nsmc|mmlu`, `run_id`=변형 이름)에 있다. rm-core에 없는 양은 `npu_lora_*`로 낸다(`npu_lora_loss`, `npu_lora_grad_norm`, `npu_lora_step_seconds` 등). **Deprecated**: 예전 처리량 시리즈 `npu_lora_tokens_per_second`(라벨 `run`)는 `airm_tok_s`와 같은 값으로 프로토콜 @@ -346,16 +348,16 @@ python tests/test_cpu.py # CPU 회귀 테스트 (NPU 불필요) 새 모델이나 SDK 버전을 올릴 때: 1. `npu-lora check`가 통과해야 한다 (장치 대 CPU 기울기). -2. 컴파일이 실패하면 `npu_lora.diagnose.first_failing_node`로 처음 실패하는 연산을 찾고, 의미를 보존하는 +2. 컴파일이 실패하면 `npu_train.diagnose.first_failing_node`로 처음 실패하는 연산을 찾고, 의미를 보존하는 재작성을 `graph.PASSES`에 추가한다. -3. 컴파일은 되는데 결과가 틀리면 `npu_lora.diagnose.compare_parts`로 컴파일된 조각마다 같은 입력에서 CPU와 비교한다. +3. 컴파일은 되는데 결과가 틀리면 `npu_train.diagnose.compare_parts`로 컴파일된 조각마다 같은 입력에서 CPU와 비교한다. `scripts/vendor-into-lmd-top.sh`는 패키지를 lmd-top에 복사한다. lmd-top은 이를 자신이 만드는 Job에 넣는다. ### 한계 -- Llama 구조만 지원한다. 다른 구조는 레이어를 순수 함수로 작성해야 한다(`npu_lora/llama.py` 참조). +- Llama 구조만 지원한다. 다른 구조는 레이어를 순수 함수로 작성해야 한다(`npu_train/llama.py` 참조). - micro-batch당 패킹된 시퀀스 1개다. 더 큰 배치는 `--accum`을 쓴다. - 첫 컴파일이 느리고(8B 모델은 수 시간) 시퀀스 길이마다 따로 컴파일한다. - furiosa-torch는 privileged 컨테이너에서만 장치를 연다. diff --git a/README.md b/README.md index 4597afb..3931db7 100644 --- a/README.md +++ b/README.md @@ -30,9 +30,11 @@ --- -Formerly the `npu-lora` repository; the Python package, CLI and Helm chart keep the name `npu-lora`. +Formerly the `npu-lora` repository. The Python package is `npu_train` (distribution `npu-train`); the old package +name `npu_lora` still imports and runs (`python -m npu_lora.`) as a deprecated alias. The CLI command +`npu-lora` (also installed as `npu-train`), the ai-rm workload name and the Helm chart keep the name `npu-lora`. -**npu-train** (Python package and CLI `npu-lora`) trains Llama-family models on the FuriosaAI RNGD — an accelerator whose SDK ships only an +**npu-train** (Python package `npu_train`, CLI `npu-lora`) trains Llama-family models on the FuriosaAI RNGD — an accelerator whose SDK ships only an inference compiler and runtime. It turns the backward pass into ordinary compiled graphs, keeps the optimizer on the host, and produces standard Hugging Face artifacts (PEFT adapters or full models) that vLLM and llm-d serve as-is. The same code runs on CUDA and CPU for reference runs. @@ -120,7 +122,7 @@ Requirements: apt-get install -y gcc-aarch64-linux-gnu libc6-dev-arm64-cross libc6-dev ``` -- **Evaluation:** `vllm` (only for `npu_lora.bench`). +- **Evaluation:** `vllm` (only for `npu_train.bench`). ```bash # 1. Verify that RNGD gradients match the CPU reference (first 2 layers). @@ -175,9 +177,9 @@ a ServiceMonitor. [lmd-top](https://github.com/ai-computing/lmd-top) builds the
```python -from npu_lora import LoRATrainer, LoRAConfig, TrainConfig -from npu_lora import backends, export -from npu_lora.data import PackedDataset +from npu_train import LoRATrainer, LoRAConfig, TrainConfig +from npu_train import backends, export +from npu_train.data import PackedDataset from transformers import AutoTokenizer trainer = LoRATrainer( @@ -210,9 +212,9 @@ export.save_peft(trainer, "./adapter") # export.save_full(...) with TrainCo | `npu-lora serve` | Load an adapter into a running vLLM | | `npu-lora eval` | Keyword-accuracy evaluation against an OpenAI-compatible endpoint | | `npu-lora job` | Print a Kubernetes training Job | -| `python -m npu_lora.bench` | vLLM offline benchmark of base / adapters / full models | -| `python -m npu_lora.compare` | Compare two training loss curves | -| `python -m npu_lora.hwprobe` | Measure host↔device bandwidth and transfer/compute overlap | +| `python -m npu_train.bench` | vLLM offline benchmark of base / adapters / full models | +| `python -m npu_train.compare` | Compare two training loss curves | +| `python -m npu_train.hwprobe` | Measure host↔device bandwidth and transfer/compute overlap | Frequently used training options: @@ -238,7 +240,7 @@ FuriosaAI RNGD, an inference-only ahead-of-time-compiled NPU, and produces HF PE | Entry-point group | Name | Object | | --- | --- | --- | -| `airm.workloads` | `npu-lora` | `npu_lora.airm:train` | +| `airm.workloads` | `npu-lora` | `npu_train.airm:train` | ```python from rm_core import Workload, registry @@ -296,7 +298,7 @@ helm template deploy/helm/npu-lora --set train.model=… --set train.data=… Using rm-core `METRICS` names: training throughput `tok_s` (Prometheus `airm_tok_s`, tags `component=npu-lora`, `workload=train`, `model`, `vendor`, `run_id`; also in the runner's return value). -Accuracy `accuracy_pct` is in the `measurements` of the `npu_lora.bench` result JSON (tag +Accuracy `accuracy_pct` is in the `measurements` of the `npu_train.bench` result JSON (tag `dataset=nsmc|mmlu`, `run_id` = variant name). Quantities not in rm-core are exported as `npu_lora_*` (`npu_lora_loss`, `npu_lora_grad_norm`, `npu_lora_step_seconds`, …). @@ -351,9 +353,9 @@ synchronous update. When you bring up a new model or SDK version: 1. `npu-lora check` must pass (device vs CPU gradients). -2. If compilation fails, `npu_lora.diagnose.first_failing_node` locates the first failing op; add a +2. If compilation fails, `npu_train.diagnose.first_failing_node` locates the first failing op; add a semantics-preserving rewrite to `graph.PASSES`. -3. If results are wrong but compilation succeeds, `npu_lora.diagnose.compare_parts` compares every +3. If results are wrong but compilation succeeds, `npu_train.diagnose.compare_parts` compares every compiled part with the CPU on the same inputs. `scripts/vendor-into-lmd-top.sh` copies the package into lmd-top, which embeds it in the Jobs it @@ -363,7 +365,7 @@ generates. ### Limitations - Llama architecture only. Other architectures need their layer written as a pure function - (see `npu_lora/llama.py`). + (see `npu_train/llama.py`). - One packed sequence per micro-batch; use `--accum` for larger batches. - First compilation is slow (hours for an 8B model) and is per sequence length. - furiosa-torch opens devices only from privileged containers. diff --git a/STATUS.md b/STATUS.md index 7a2d573..741d1c3 100644 --- a/STATUS.md +++ b/STATUS.md @@ -1,18 +1,18 @@ # STATUS — RNGD NPU 파인튜닝 학습 -- **State**: idle -- **Updated**: 2026-10-22 01:48+09:00 -- **Agent**: claude@control-node -- **Task**: #40 resident AdamW DRAM_SHARED OOM (done) -- **Last result**: 2 RNGD x 8 resident 200/200 steps, no OOM, 4.43 s/step (463 tok/s) steady; 1 card x 4 resident at low load 4.79 s vs 1-card offload 5.11 s, model +3.0% -- **Next**: report the runtime flag to Furiosa +- **State**: active +- **Updated**: 2026-10-22 11:54+09:00 +- **Agent**: claude@dev-host +- **Task**: #46 rename package npu_lora to npu_train +- **Last result**: package moved to npu_train; npu_lora kept as deprecated alias (same module objects, python -m npu_lora.X works); CPU tests 38/38 in a clean venv +- **Next**: maintainer review of PR; then uninstall npu-lora and pip install -e . on shared hosts - **Blockers**: none ## Goal -추론 전용 AOT 컴파일 NPU인 Furiosa RNGD에서 LoRA와 full fine-tuning을 실용 수준으로 돌리고, 같은 조건의 A100 학습과 품질·속도를 비교한다. 구현은 공개 저장소 [ai-computing/npu-lora](https://github.com/ai-computing/npu-lora)의 `npu_lora` 패키지(백엔드 furiosa/cuda/cpu)다. 결과를 llm-d 서빙(vLLM `--enable-lora`)과 lmd-top(`--plan train`)에 붙이고, ai-rm 구성요소(`ai-rm.yaml`, protocol 0.1)로 내는 것까지 범위에 넣는다 (`README.md`). +추론 전용 AOT 컴파일 NPU인 Furiosa RNGD에서 LoRA와 full fine-tuning을 실용 수준으로 돌리고, 같은 조건의 A100 학습과 품질·속도를 비교한다. 구현은 공개 저장소 [ai-computing/npu-train](https://github.com/ai-computing/npu-train)의 `npu_train` 패키지(백엔드 furiosa/cuda/cpu, 예전 이름 `npu_lora`는 deprecated 별칭으로 남김, #46)다. 결과를 llm-d 서빙(vLLM `--enable-lora`)과 lmd-top(`--plan train`)에 붙이고, ai-rm 구성요소(`ai-rm.yaml`, protocol 0.1)로 내는 것까지 범위에 넣는다 (`README.md`). ## Findings @@ -42,6 +42,12 @@ +### 2026-10-22 11:54+09:00 — #46 rename package npu_lora to npu_train + +- Result: package moved to npu_train; npu_lora kept as deprecated alias (same module objects, python -m npu_lora.X works); CPU tests 38/38 in a clean venv +- Next: maintainer review of PR; then uninstall npu-lora and pip install -e . on shared hosts +- By: claude@dev-host · refactor/46-npu-train-package@af1e8f5-dirty + ### 2026-10-22 01:48+09:00 — #40 resident AdamW DRAM_SHARED OOM ✅ - Result: 2 RNGD x 8 resident 200/200 steps, no OOM, 4.43 s/step (463 tok/s) steady; 1 card x 4 resident at low load 4.79 s vs 1-card offload 5.11 s, model +3.0% diff --git a/ai-rm.yaml b/ai-rm.yaml index 3b461fa..058ada0 100644 --- a/ai-rm.yaml +++ b/ai-rm.yaml @@ -13,13 +13,13 @@ test: - python -m rm_core.conformance adapters: - group: airm.workloads - name: npu-lora # npu_lora.airm:train + name: npu-lora # npu_train.airm:train deploy: target: k8s chart: deploy/helm/npu-lora # 학습 Job (helm template deploy/helm/npu-lora) manifests: null # `npu-lora job ... | kubectl apply -f -` 도 같은 Job 을 만든다 image: null # 공식 furiosaai/furiosa-llm 이미지 + 시작 시 pip 설치(chart) 또는 ConfigMap(CLI) -metrics: [tok_s, accuracy_pct] # tok_s: 학습 처리량(workload=train), accuracy_pct: npu_lora.bench(dataset=nsmc|mmlu) +metrics: [tok_s, accuracy_pct] # tok_s: 학습 처리량(workload=train), accuracy_pct: npu_train.bench(dataset=nsmc|mmlu) config: env_example: cluster.example.env interfaces: diff --git a/deploy/helm/npu-lora/templates/job.yaml b/deploy/helm/npu-lora/templates/job.yaml index 45b06f8..f6a8d53 100644 --- a/deploy/helm/npu-lora/templates/job.yaml +++ b/deploy/helm/npu-lora/templates/job.yaml @@ -78,7 +78,7 @@ spec: - -- - python3 - -m - - npu_lora.cli + - npu_train.cli - train - --model - {{ $t.model | quote }} diff --git a/deploy/helm/npu-lora/values.yaml b/deploy/helm/npu-lora/values.yaml index 62a73cf..f4e2ffb 100644 --- a/deploy/helm/npu-lora/values.yaml +++ b/deploy/helm/npu-lora/values.yaml @@ -5,7 +5,7 @@ enabled: true image: {repository: furiosaai/furiosa-llm, tag: latest} imagePullPolicy: IfNotPresent install: - pip: "npu-lora @ git+https://github.com/ai-computing/npu-train@main" + pip: "npu-train @ git+https://github.com/ai-computing/npu-train@main" airm: kvTransport: "" # 학습에는 KV 경로가 없다 — 비워 둔다 placement: "" diff --git a/docs/compiler-workarounds.md b/docs/compiler-workarounds.md index f624a4e..795642c 100644 --- a/docs/compiler-workarounds.md +++ b/docs/compiler-workarounds.md @@ -1,6 +1,6 @@ # 컴파일러 우회 (furiosa-torch 2026.2) -추론용 AOT 컴파일러에 역전파 그래프를 넣으면서 만난 문제와 대응이다. 모든 대응은 `npu_lora/graph.py` 의 +추론용 AOT 컴파일러에 역전파 그래프를 넣으면서 만난 문제와 대응이다. 모든 대응은 `npu_train/graph.py` 의 그래프 변환(`graph.PASSES`) 또는 분할·컴파일 단계에 있고, `graph.build` 가 변환 전후 결과를 CPU 로 비교한다. | 증상 | 원인 | 대응 | @@ -25,11 +25,11 @@ | 컴파일은 되는데 실행이 `failed to run TP program … TimedOut` | 전치 view 위에서 위치 축 mean + sub + bmm 이 한 조각에 있을 때 | mean 을 전치 전 `[S, nkv, hd]` 에서 | | `npu-lora check` 가 엉뚱한 차이 | furiosa 를 불러오면 `torch.manual_seed` 가 장치 쪽에서 경고만 내고 끝나 두 trainer 의 A 초기값이 달라짐 | 어댑터 초기화에 전용 `torch.Generator` | -값이 틀리는 문제는 컴파일 오류가 나지 않는다. `npu_lora.diagnose.compare_parts` 로 조각마다 CPU 와 장치 출력을 +값이 틀리는 문제는 컴파일 오류가 나지 않는다. `npu_train.diagnose.compare_parts` 로 조각마다 CPU 와 장치 출력을 같은 입력으로 비교한다. 학습 루프의 컴파일 샘플은 0/상수라서 이런 문제를 잡지 못하니, 새 모델은 반드시 `npu-lora check` 로 기울기를 비교한다. -새 모델·새 SDK 에서 컴파일이 실패하면 `npu_lora.diagnose.first_failing_node` 로 처음 실패하는 노드를 찾고, +새 모델·새 SDK 에서 컴파일이 실패하면 `npu_train.diagnose.first_failing_node` 로 처음 실패하는 노드를 찾고, 의미를 바꾸지 않는 변환을 `graph.PASSES` 에 넣는다. 모든 변환은 `graph.build` 가 CPU 재실행으로 결과가 같은지 확인한다. diff --git a/docs/validation.md b/docs/validation.md index 3be0612..25a40b3 100644 --- a/docs/validation.md +++ b/docs/validation.md @@ -15,7 +15,7 @@ ## 품질 검증 — RNGD 학습이 GPU 학습과 같은 품질인가 (2026-10-16) 같은 데이터(NSMC 학습 4,000개 중 1,600개 분량)·시드·설정(S=128, r=16 q·v, lr 2e-4, 400 step × accum 4)으로 -RNGD 와 A100 에서 각각 LoRA 학습 → vLLM 오프라인 평가(`npu_lora.bench`). +RNGD 와 A100 에서 각각 LoRA 학습 → vLLM 오프라인 평가(`npu_train.bench`). | | NSMC 시험 정답률 (n=1000, 학습에 없는 리뷰) | MMLU (n=400, 망각 확인) | | --- | --- | --- | @@ -24,11 +24,11 @@ RNGD 와 A100 에서 각각 LoRA 학습 → vLLM 오프라인 평가(`npu_lora.b | A100 학습 LoRA (같은 조건) | 87.6% | 63.0% | loss 곡선(40 step 구간 평균)의 RNGD/A100 상대차 중앙값 2.8%, 마지막 구간 0.0676 vs 0.0667 -(`python -m npu_lora.compare a/train.jsonl b/train.jsonl`). 이 검증 과정에서 고친 품질 버그: +(`python -m npu_train.compare a/train.jsonl b/train.jsonl`). 이 검증 과정에서 고친 품질 버그: Llama 3.x 의 llama3 `rope_scaling` 미적용(HF 대비 1.8e-4 → 3.5e-7), 긴 예제가 응답째 잘리던 문제(이제 프롬프트 쪽을 덜어냄). 재현: `examples/nsmc.py OUT` → `lmd-top --plan train … --set data=…/nsmc_train.jsonl` → -`python -m npu_lora.bench --model … --adapter rngd=… --task nsmc=… --task mmlu=…`. +`python -m npu_train.bench --model … --adapter rngd=… --task nsmc=… --task mmlu=…`. 데모: `examples/cluster_facts.py` 로 만든 데이터를 `lmd-top --plan train … --apply` 로 학습하고 `npu-lora serve` → `npu-lora eval`. diff --git a/npu_lora/__init__.py b/npu_lora/__init__.py index 30e2b30..47c5c5d 100644 --- a/npu_lora/__init__.py +++ b/npu_lora/__init__.py @@ -1,16 +1,85 @@ -"""npu-lora: AOT 컴파일 NPU(FuriosaAI RNGD 등)에서 LoRA 파인튜닝. +"""Deprecated alias of `npu_train` (the package was renamed from `npu_lora`). -추론 전용으로 나온 NPU 에서 학습이 되는 이유: 고정 가중치와 LoRA 어댑터를 모두 그래프 입력으로 두고, -순전파+역전파를 make_fx 로 펼친 그래프 하나를 컴파일한다. 옵티마이저는 host 에서 돈다. +`import npu_lora.X` returns the very module object `npu_train.X` (one copy of every class and global), and +`python -m npu_lora.X ...` runs `npu_train.X` as `__main__`, so existing commands keep working. A file that +exists only under this directory (for example a script copied here on a host) is still found here. + +Use `npu_train` in new code. This alias is removed in a later release. """ -__version__ = "0.1.0" +import importlib +import importlib.abc +import importlib.machinery +import importlib.util +import os +import sys +import warnings + +warnings.warn("the package npu_lora was renamed to npu_train; `import npu_lora` and `python -m npu_lora.` " + "still work but are deprecated", DeprecationWarning, stacklevel=2) + +_OLD, _NEW = "npu_lora", "npu_train" -_LAZY = {"LoRAConfig", "LoRATrainer", "TrainConfig"} +try: + import npu_train as _pkg +except ImportError: + # An editable install made before the rename maps only `npu_lora` to its directory; npu_train sits next to it. + sys.path.append(os.path.dirname(os.path.dirname(os.path.realpath(__file__)))) + import npu_train as _pkg + +__version__ = _pkg.__version__ def __getattr__(name): - # torch 없이도 `npu-lora job`(매니페스트 출력)이 돌도록 학습 모듈은 쓸 때 불러온다. - if name in _LAZY: - from . import trainer - return getattr(trainer, name) - raise AttributeError(name) + # Top-level names (LoRATrainer, ... are lazy in npu_train) come from npu_train. + return getattr(_pkg, name) + + +class _AliasLoader(importlib.abc.Loader): + """Loads `npu_lora.X` as the already-importable module `npu_train.X`.""" + + def __init__(self, real_name, real_spec): + self.real_name, self.real_spec = real_name, real_spec + + def create_module(self, spec): + mod = importlib.import_module(self.real_name) + self._real_module_spec = mod.__spec__ + return mod + + def exec_module(self, module): + # The import system sets module.__spec__ to the alias spec; keep the module's own (pickle, reload, spawn). + module.__spec__ = self._real_module_spec + + # runpy (`python -m npu_lora.X`) asks the loader for the code and runs it as __main__. + def get_code(self, fullname): + return self.real_spec.loader.get_code(self.real_name) + + def get_source(self, fullname): + return self.real_spec.loader.get_source(self.real_name) + + def is_package(self, fullname): + return self.real_spec.submodule_search_locations is not None + + def get_filename(self, fullname): + return self.real_spec.origin + + +class _AliasFinder(importlib.abc.MetaPathFinder): + def find_spec(self, fullname, path=None, target=None): + if not fullname.startswith(_OLD + "."): + return None + real_name = _NEW + fullname[len(_OLD):] + try: + real = importlib.util.find_spec(real_name) + except (ImportError, ValueError): + real = None + if real is None: + return None # not in npu_train: fall through to files under npu_lora/ + spec = importlib.machinery.ModuleSpec(fullname, _AliasLoader(real_name, real), origin=real.origin, + is_package=real.submodule_search_locations is not None) + if real.submodule_search_locations is not None: + spec.submodule_search_locations = list(real.submodule_search_locations) + return spec + + +if not any(isinstance(f, _AliasFinder) for f in sys.meta_path): + sys.meta_path.insert(0, _AliasFinder()) diff --git a/npu_train/__init__.py b/npu_train/__init__.py new file mode 100644 index 0000000..bb59ebd --- /dev/null +++ b/npu_train/__init__.py @@ -0,0 +1,16 @@ +"""npu-train (패키지 npu_train, 예전 이름 npu_lora): AOT 컴파일 NPU(FuriosaAI RNGD 등)에서 LoRA·전체 파인튜닝. + +추론 전용으로 나온 NPU 에서 학습이 되는 이유: 고정 가중치와 LoRA 어댑터를 모두 그래프 입력으로 두고, +순전파+역전파를 make_fx 로 펼친 그래프 하나를 컴파일한다. 옵티마이저는 host 에서 돈다. +""" +__version__ = "0.1.0" + +_LAZY = {"LoRAConfig", "LoRATrainer", "TrainConfig"} + + +def __getattr__(name): + # torch 없이도 `npu-lora job`(매니페스트 출력)이 돌도록 학습 모듈은 쓸 때 불러온다. + if name in _LAZY: + from . import trainer + return getattr(trainer, name) + raise AttributeError(name) diff --git a/npu_lora/airm.py b/npu_train/airm.py similarity index 97% rename from npu_lora/airm.py rename to npu_train/airm.py index a2cf890..623053f 100644 --- a/npu_lora/airm.py +++ b/npu_train/airm.py @@ -78,6 +78,6 @@ def _check_keys(cfg): raise ContractError(f"unknown npu-lora config keys {sorted(unknown)}; allowed {KEYS}") -# entry point 는 npu_lora.airm:train. rm_core.registry.load 가 등록된 객체를 (설정 기본값 kwargs 로) 호출해 +# entry point 는 npu_train.airm:train. rm_core.registry.load 가 등록된 객체를 (설정 기본값 kwargs 로) 호출해 # 인스턴스를 만들므로, 클래스를 그 이름으로 내보낸다 — 로드 결과가 (Workload, **설정) 을 받는 runner 다. train = TrainRunner diff --git a/npu_lora/backends.py b/npu_train/backends.py similarity index 100% rename from npu_lora/backends.py rename to npu_train/backends.py diff --git a/npu_lora/bench.py b/npu_train/bench.py similarity index 98% rename from npu_lora/bench.py rename to npu_train/bench.py index 7e78b1c..3178bca 100644 --- a/npu_lora/bench.py +++ b/npu_train/bench.py @@ -2,7 +2,7 @@ vLLM 이미지(GPU)에서 돈다. 한 번 실행에 모델 하나, 그 위에 LoRA 어댑터 여러 개. - python -m npu_lora.bench --model meta-llama/Llama-3.1-8B-Instruct \ + python -m npu_train.bench --model meta-llama/Llama-3.1-8B-Instruct \ --adapter npu=/mnt/store/adapters/.../nsmc-rngd --adapter gpu=/mnt/store/adapters/.../nsmc-cuda \ --task nsmc=nsmc_test.jsonl --task mmlu=mmlu.jsonl --out result.json diff --git a/npu_lora/cli.py b/npu_train/cli.py similarity index 100% rename from npu_lora/cli.py rename to npu_train/cli.py diff --git a/npu_lora/compare.py b/npu_train/compare.py similarity index 96% rename from npu_lora/compare.py rename to npu_train/compare.py index d5048ba..469d57c 100644 --- a/npu_lora/compare.py +++ b/npu_train/compare.py @@ -1,6 +1,6 @@ """두 학습 로그(JSONL)의 loss 곡선 비교 — 같은 데이터·시드·설정이면 장치가 달라도 곡선이 겹쳐야 한다. - python -m npu_lora.compare a/train.jsonl b/train.jsonl [--window 20] + python -m npu_train.compare a/train.jsonl b/train.jsonl [--window 20] 출력: step 구간별 평균 loss(두 곡선), 구간 평균의 상대차, 전체 지표. bf16 연산 순서가 장치마다 달라 step 단위로는 갈라지지만(혼돈적 누적), 구간 평균은 오차 범위 안이어야 한다. diff --git a/npu_lora/data.py b/npu_train/data.py similarity index 100% rename from npu_lora/data.py rename to npu_train/data.py diff --git a/npu_lora/diagnose.py b/npu_train/diagnose.py similarity index 100% rename from npu_lora/diagnose.py rename to npu_train/diagnose.py diff --git a/npu_lora/export.py b/npu_train/export.py similarity index 100% rename from npu_lora/export.py rename to npu_train/export.py diff --git a/npu_lora/graph.py b/npu_train/graph.py similarity index 100% rename from npu_lora/graph.py rename to npu_train/graph.py diff --git a/npu_lora/hwprobe.py b/npu_train/hwprobe.py similarity index 97% rename from npu_lora/hwprobe.py rename to npu_train/hwprobe.py index d6cba05..5d99e5d 100644 --- a/npu_lora/hwprobe.py +++ b/npu_train/hwprobe.py @@ -1,6 +1,6 @@ """장치 전송 대역폭과 '계산 중 전송' 겹치기 가능 여부를 잰다 — offload 학습 설계의 입력값. - python -m npu_lora.hwprobe --backend furiosa:auto + python -m npu_train.hwprobe --backend furiosa:auto """ import argparse import json diff --git a/npu_lora/k8s.py b/npu_train/k8s.py similarity index 96% rename from npu_lora/k8s.py rename to npu_train/k8s.py index 72611ac..348be96 100644 --- a/npu_lora/k8s.py +++ b/npu_train/k8s.py @@ -25,7 +25,7 @@ def _name(s): def package_files(): - """npu_lora 패키지의 .py 파일 {이름: 내용} — ConfigMap 키가 된다(하위 디렉터리 없음).""" + """npu_train 패키지의 .py 파일 {이름: 내용} — ConfigMap 키가 된다(하위 디렉터리 없음).""" here = os.path.dirname(os.path.abspath(__file__)) return {f: open(os.path.join(here, f)).read() for f in sorted(os.listdir(here)) if f.endswith(".py")} @@ -43,7 +43,7 @@ def build(model, data, name, ns="llm-serving", steps=100, seq_len=512, rank=16, repo_dir = model.replace("/", "--") out_dir = out_dir or f"{STORE}/adapters/{repo_dir}/{run}" labels = {"app": app, "app.kubernetes.io/part-of": "npu-lora", "npu-lora/run": run} - cmd = ["python3", "-m", "npu_lora.cli", "train", + cmd = ["python3", "-m", "npu_train.cli", "train", "--model", model, "--data", data, "--out", out_dir, "--backend", "furiosa:auto", "--steps", str(steps), "--seq-len", str(seq_len), "--rank", str(rank), "--alpha", str(alpha), "--targets", targets, "--lr", str(lr), "--accum", str(accum), @@ -58,7 +58,7 @@ def build(model, data, name, ns="llm-serving", steps=100, seq_len=512, rank=16, {"name": "src", "configMap": {"name": f"{app}-src"}}, {"name": "store", "persistentVolumeClaim": {"claimName": STORE_PVC}}] mounts = [{"name": "rngd", "mountPath": "/run/npu-lora"}, - {"name": "src", "mountPath": "/opt/npu-lora/npu_lora", "readOnly": True}, + {"name": "src", "mountPath": "/opt/npu-lora/npu_train", "readOnly": True}, {"name": "store", "mountPath": STORE}] env = [{"name": "PYTHONPATH", "value": "/opt/npu-lora"}, {"name": "NPU_LORA_RNGD_FILE", "value": "/run/npu-lora/rngd"}, diff --git a/npu_lora/llama.py b/npu_train/llama.py similarity index 100% rename from npu_lora/llama.py rename to npu_train/llama.py diff --git a/npu_lora/metrics.py b/npu_train/metrics.py similarity index 100% rename from npu_lora/metrics.py rename to npu_train/metrics.py diff --git a/npu_lora/moe.py b/npu_train/moe.py similarity index 100% rename from npu_lora/moe.py rename to npu_train/moe.py diff --git a/npu_lora/moe_device.py b/npu_train/moe_device.py similarity index 100% rename from npu_lora/moe_device.py rename to npu_train/moe_device.py diff --git a/npu_lora/optim_device.py b/npu_train/optim_device.py similarity index 99% rename from npu_lora/optim_device.py rename to npu_train/optim_device.py index 27dff98..63720fc 100644 --- a/npu_lora/optim_device.py +++ b/npu_train/optim_device.py @@ -9,7 +9,7 @@ 레이어 가중치는 [CHUNK_ROWS, CHUNK_COLS] 조각으로 나눠 같은 그래프 하나로 돈다(norm 은 [1, hidden] 그래프). 두 변형: fp32(상태·master 모두 fp32, 다음 순전파용 bf16 w' 도 낸다), bf16(w·m·v 모두 bf16, 계산은 fp32). - python -m npu_lora.optim_device --backend furiosa:auto --model meta-llama/Llama-3.1-8B-Instruct --out r.json + python -m npu_train.optim_device --backend furiosa:auto --model meta-llama/Llama-3.1-8B-Instruct --out r.json """ import argparse import json diff --git a/npu_lora/trainer.py b/npu_train/trainer.py similarity index 100% rename from npu_lora/trainer.py rename to npu_train/trainer.py diff --git a/pyproject.toml b/pyproject.toml index 0932a2a..350b23d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [project] -name = "npu-lora" +name = "npu-train" version = "0.1.0" description = "LoRA fine-tuning on ahead-of-time compiled NPUs (FuriosaAI RNGD), with CPU/CUDA reference backends" requires-python = ">=3.10" @@ -10,14 +10,16 @@ dependencies = ["torch>=2.4", "safetensors", "transformers>=5.1", "huggingface_h # furiosa-torch 는 Furiosa SDK 와 함께 설치된다(PyPI 버전 고정 필요 없음). [project.scripts] -npu-lora = "npu_lora.cli:main" +npu-train = "npu_train.cli:main" +npu-lora = "npu_train.cli:main" # old command name, kept [project.entry-points."airm.workloads"] -npu-lora = "npu_lora.airm:train" +npu-lora = "npu_train.airm:train" [build-system] requires = ["setuptools>=64"] build-backend = "setuptools.build_meta" [tool.setuptools.packages.find] -include = ["npu_lora*"] +# npu_lora: deprecated alias of npu_train (compatibility shim), kept for existing commands +include = ["npu_train*", "npu_lora*"] diff --git a/scripts/vendor-into-lmd-top.sh b/scripts/vendor-into-lmd-top.sh index 8729d36..1393424 100755 --- a/scripts/vendor-into-lmd-top.sh +++ b/scripts/vendor-into-lmd-top.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# npu_lora 패키지 소스를 lmd-top 의 assets/npu-lora/ 로 복사한다. +# npu_train 패키지 소스를 lmd-top 의 assets/npu-lora/ 로 복사한다. # lmd-top 은 이 파일들을 include_str! 로 바이너리에 넣고, `--plan train` 이 만드는 Job 의 # ConfigMap 으로 내보낸다 — lmd-top 바이너리 하나로 학습 Job 을 만들 수 있게 하려는 것이다. # usage: scripts/vendor-into-lmd-top.sh [LMD_TOP_DIR] @@ -8,8 +8,8 @@ here="$(cd "$(dirname "$0")/.." && pwd)" dst="${1:-$here/../lmd-top}/assets/npu-lora" mkdir -p "$dst" rm -f "$dst"/*.py -cp "$here"/npu_lora/*.py "$dst"/ -ver=$(python3 -c "import re;print(re.search(r'__version__ = \"([^\"]+)\"', open('$here/npu_lora/__init__.py').read()).group(1))") +cp "$here"/npu_train/*.py "$dst"/ +ver=$(python3 -c "import re;print(re.search(r'__version__ = \"([^\"]+)\"', open('$here/npu_train/__init__.py').read()).group(1))") src_rev=$(git -C "$here" rev-parse --short HEAD 2>/dev/null || echo "uncommitted") printf '%s\n' "$ver" > "$dst/VERSION" printf 'vendored from npu-lora %s (%s) by scripts/vendor-into-lmd-top.sh — edit upstream, not here\n' "$ver" "$src_rev" > "$dst/README" diff --git a/tests/test_airm.py b/tests/test_airm.py index ab2f96c..d895e53 100644 --- a/tests/test_airm.py +++ b/tests/test_airm.py @@ -37,7 +37,7 @@ def _tiny_model(): def test_registry_loads_runner(): - assert registry.available("airm.workloads").get("npu-lora") == "npu_lora.airm:train" + assert registry.available("airm.workloads").get("npu-lora") == "npu_train.airm:train" run = registry.load("airm.workloads", "npu-lora") assert callable(run) and run.name == "npu-lora" @@ -74,7 +74,7 @@ def test_rejects_wrong_workload_and_config(): def test_prometheus_uses_airm_name_and_tag_keys(): from rm_core.metrics import TAG_KEYS - from npu_lora.metrics import Reporter + from npu_train.metrics import Reporter rep = Reporter("r", airm_tags={"component": "npu-lora", "workload": "train", "run_id": "r"}) rep.step({"step": 1, "loss": 1.0, "grad_norm": 1.0, "lr": 1e-4, "tokens": 32, "seconds": 0.5, "tokens_per_s": 64.0}) diff --git a/tests/test_compat.py b/tests/test_compat.py new file mode 100644 index 0000000..771ba2e --- /dev/null +++ b/tests/test_compat.py @@ -0,0 +1,101 @@ +"""The old package name npu_lora keeps working after the rename to npu_train (issue #46). + +Every check runs in a fresh interpreter so the one-time DeprecationWarning and the import state are not shared +with the other tests. +""" +import os +import shutil +import subprocess +import sys +import textwrap + +ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) + + +def _run(args, cwd=ROOT, **kw): + env = {**os.environ, "CUDA_VISIBLE_DEVICES": ""} + return subprocess.run([sys.executable, *args], cwd=cwd, env=env, capture_output=True, text=True, timeout=300, **kw) + + +def test_import_old_name_is_same_module_and_warns(): + code = textwrap.dedent(""" + import sys, warnings + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + import npu_lora.metrics + assert any(issubclass(x.category, DeprecationWarning) and "npu_train" in str(x.message) for x in w), w + import npu_train, npu_train.metrics + assert npu_lora.metrics is npu_train.metrics + assert sys.modules["npu_lora.metrics"] is npu_train.metrics + assert npu_train.metrics.__spec__.name == "npu_train.metrics" + from npu_lora.k8s import build + from npu_train.k8s import build as build2 + assert build is build2 + import npu_lora + assert npu_lora.__version__ == npu_train.__version__ + assert npu_lora.LoRAConfig is npu_train.LoRAConfig # lazy top-level names + try: + import npu_lora.no_such_module + except ModuleNotFoundError: + pass + else: + raise AssertionError("missing module imported") + print("ok") + """) + r = _run(["-c", code]) + assert r.returncode == 0, r.stderr + assert r.stdout.strip() == "ok" + + +def test_python_m_old_name_runs(): + r = _run(["-m", "npu_lora.cli", "--help"]) + assert r.returncode == 0, r.stderr + assert "usage:" in r.stdout and "train" in r.stdout + r = _run(["-m", "npu_lora.compare", "--help"]) + assert r.returncode == 0, r.stderr + assert "usage:" in r.stdout + + +def _copy_tree(dst): + for pkg in ("npu_train", "npu_lora"): + shutil.copytree(os.path.join(ROOT, pkg), os.path.join(dst, pkg), + ignore=shutil.ignore_patterns("__pycache__")) + + +def test_script_left_in_old_directory_still_runs(tmp_path): + # A script copied into npu_lora/ on a host (e.g. moe_bench2.py) imports through the alias and runs with -m. + _copy_tree(tmp_path) + (tmp_path / "npu_lora" / "local_tool.py").write_text(textwrap.dedent(""" + import sys + from npu_lora.k8s import build + from .compare import main + import npu_train.k8s + assert build is npu_train.k8s.build + print("local", __name__, sys.argv[1:]) + """)) + # -S -E -s: stdlib only, so npu_train comes from the copied tree, not from an install + r = _run(["-S", "-E", "-s", "-m", "npu_lora.local_tool", "x"], cwd=str(tmp_path)) + assert r.returncode == 0, r.stderr + assert r.stdout.strip() == "local __main__ ['x']" + + +def test_old_editable_install_mapping_only_npu_lora(tmp_path): + # An editable install made before the rename maps only `npu_lora` to its directory; npu_train is not on sys.path. + _copy_tree(tmp_path / "repo") + code = textwrap.dedent(f""" + import importlib.util, sys + class OldEditableFinder: + @classmethod + def find_spec(cls, name, path=None, target=None): + if name == "npu_lora": + d = {str(tmp_path / "repo" / "npu_lora")!r} + return importlib.util.spec_from_file_location(name, d + "/__init__.py", + submodule_search_locations=[d]) + sys.meta_path.append(OldEditableFinder) + import npu_lora.k8s, npu_train.k8s + assert npu_lora.k8s is npu_train.k8s + print("ok") + """) + r = _run(["-S", "-E", "-s", "-c", code], cwd=str(tmp_path)) + assert r.returncode == 0, r.stderr + assert r.stdout.strip() == "ok" diff --git a/tests/test_cpu.py b/tests/test_cpu.py index 4e3f65b..065b3bd 100644 --- a/tests/test_cpu.py +++ b/tests/test_cpu.py @@ -12,8 +12,8 @@ import torch sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) -from npu_lora import graph as G, llama as L # noqa: E402 -from npu_lora.backends import CPUBackend # noqa: E402 +from npu_train import graph as G, llama as L # noqa: E402 +from npu_train.backends import CPUBackend # noqa: E402 BF = torch.bfloat16 C = L.LlamaConfig(H=64, I=128, nh=4, nkv=2, n_layers=2, vocab=1000) @@ -66,7 +66,7 @@ def test_full_pipeline_equals_sync_update(): from transformers import LlamaConfig, LlamaForCausalLM except ImportError: return - from npu_lora import LoRATrainer, LoRAConfig, TrainConfig + from npu_train import LoRATrainer, LoRAConfig, TrainConfig d = tempfile.mkdtemp() cfg = LlamaConfig(hidden_size=64, intermediate_size=128, num_attention_heads=4, num_key_value_heads=2, num_hidden_layers=2, vocab_size=1000, rope_theta=500000.0, tie_word_embeddings=False) @@ -113,7 +113,7 @@ def test_two_device_split_and_resident_adamw_equal_one_device(): from transformers import LlamaConfig, LlamaForCausalLM except ImportError: return - from npu_lora import LoRATrainer, LoRAConfig, TrainConfig + from npu_train import LoRATrainer, LoRAConfig, TrainConfig d = tempfile.mkdtemp() cfg = LlamaConfig(hidden_size=64, intermediate_size=128, num_attention_heads=4, num_key_value_heads=2, num_hidden_layers=4, vocab_size=1000, rope_theta=500000.0, tie_word_embeddings=False) @@ -161,7 +161,7 @@ def test_resident_shapes_from_header(): from transformers import LlamaConfig, LlamaForCausalLM except ImportError: return - from npu_lora.optim_device import resident_shapes + from npu_train.optim_device import resident_shapes d = tempfile.mkdtemp() cfg = LlamaConfig(hidden_size=64, intermediate_size=128, num_attention_heads=4, num_key_value_heads=2, num_hidden_layers=1, vocab_size=1000, tie_word_embeddings=False) @@ -173,7 +173,7 @@ def test_resident_shapes_from_header(): def test_visible_chip_picks_the_nth_assigned_card(): """a two-card pod on a node with npu0, npu1, npu3 assigned npu1,npu3 -> ordinals 1 and 2""" - from npu_lora import backends + from npu_train import backends real, env = backends.os.listdir, dict(os.environ) backends.os.listdir = lambda d: ["npu0", "npu0pe0", "npu1", "npu1mgmt", "npu3", "npu3pe0-1"] try: @@ -190,7 +190,7 @@ def test_visible_chip_picks_the_nth_assigned_card(): def test_serialized_graph_call_holds_the_device_alloc_lock(): """#40: a compiled graph call must not overlap a device allocation from a layer worker thread""" import threading - from npu_lora import backends + from npu_train import backends class Mod: tag = "edf" @@ -243,7 +243,7 @@ def test_fold_neg_add_keeps_silu_backward(): def test_rbln_host_parts_place_groups_on_the_host(monkeypatch): import types - from npu_lora.backends import RBLNBackend + from npu_train.backends import RBLNBackend monkeypatch.setitem(sys.modules, "rebel", types.SimpleNamespace(__version__="test")) assert RBLNBackend.group("llama-v2-H64-S32-r4-qv-s2.0-fwd-p3of15-ab12cd") == "fwd" assert RBLNBackend.group("llama-v3-H64-S32-Vc1000-rms-bwd-p0of1-ab12cd") == "rms-bwd" @@ -319,7 +319,7 @@ def test_hf_equivalence(): from transformers import LlamaConfig, LlamaForCausalLM except ImportError: return - from npu_lora import LoRAConfig, LoRATrainer, TrainConfig, export + from npu_train import LoRAConfig, LoRATrainer, TrainConfig, export d = tempfile.mkdtemp() cfg = LlamaConfig(hidden_size=64, intermediate_size=128, num_attention_heads=4, num_key_value_heads=2, num_hidden_layers=2, vocab_size=1000, rope_theta=500000.0, tie_word_embeddings=False) @@ -352,7 +352,7 @@ def test_hf_equivalence(): def _family_equivalence(family, extra): """LoRA on q,k,v,o through the functional block vs the merged model in transformers (CPU).""" from transformers import AutoConfig, AutoModelForCausalLM - from npu_lora import LoRAConfig, LoRATrainer, TrainConfig, export + from npu_train import LoRAConfig, LoRATrainer, TrainConfig, export d = tempfile.mkdtemp() cfg = AutoConfig.for_model(family, hidden_size=64, intermediate_size=128, num_attention_heads=4, num_key_value_heads=2, num_hidden_layers=2, vocab_size=1000, @@ -424,7 +424,7 @@ def _moe_equivalence(family, extra): dL/dB = s·(dL/dW')·Aᵀ and dL/dA = s·Bᵀ·(dL/dW'). initializer_range=0.2 makes the MoE block matter: with the block's Jacobian dropped the gradients are off by ~0.9 (relative), with it by ~0.03 (bf16).""" from transformers import AutoConfig, AutoModelForCausalLM - from npu_lora import LoRAConfig, LoRATrainer, TrainConfig, export + from npu_train import LoRAConfig, LoRATrainer, TrainConfig, export d = tempfile.mkdtemp() cfg = AutoConfig.for_model(family, hidden_size=64, intermediate_size=128, num_attention_heads=4, num_key_value_heads=2, num_hidden_layers=2, vocab_size=1000, @@ -481,8 +481,8 @@ def test_moe_full_finetuning(): """MoE full fine-tuning: attention on the device path, norm + router + experts trained on the host. The host part's gradients equal HF autograd; loss decreases; the export loads in HF with the trainer's loss.""" from transformers import AutoConfig, AutoModelForCausalLM - from npu_lora import LoRAConfig, LoRATrainer, TrainConfig, export - from npu_lora import moe as M + from npu_train import LoRAConfig, LoRATrainer, TrainConfig, export + from npu_train import moe as M d = tempfile.mkdtemp() cfg = AutoConfig.for_model("qwen3_moe", hidden_size=64, intermediate_size=128, num_attention_heads=4, num_key_value_heads=2, num_hidden_layers=2, vocab_size=1000, head_dim=32, @@ -542,7 +542,7 @@ def capture(self, clip, lr, k): def test_host_mlp_keep_graph_equals_recompute(): """HostMLP.keep_graph reuses the forward's autograd graph in backward: same input gradients (LoRA) and the same host-part parameter gradients (full fine-tuning) as recomputing the forward.""" - from npu_lora import moe as M + from npu_train import moe as M d = tempfile.mkdtemp() _tiny_moe(d) g = torch.Generator().manual_seed(2) @@ -563,7 +563,7 @@ def test_host_mlp_keep_graph_equals_recompute(): def test_hot_expert_packing(): """pack(): rows grouped by hot expert, each at slot * nb + rank; nb = bucket of the busiest expert; the batched expert graph equals the per-expert host computation on the packed rows""" - from npu_lora import moe_device as MD + from npu_train import moe_device as MD slot = torch.tensor([2, 0, 2, 1, 2, 0]) order, place, nb = MD.pack(slot, 3) assert nb == 8 and slot[order].tolist() == [0, 0, 1, 2, 2, 2] @@ -589,8 +589,8 @@ def test_moe_hot_experts_on_device_match_host(): """hot experts through the device expert graphs (CPU backend) == every expert on the host: LoRA adapter gradients and full fine-tuning host-part gradients (incl. the stacked expert weights) with half of each layer's experts forced onto the device path.""" - from npu_lora import LoRAConfig, LoRATrainer, TrainConfig - from npu_lora import moe as M + from npu_train import LoRAConfig, LoRATrainer, TrainConfig + from npu_train import moe as M d = tempfile.mkdtemp() _tiny_moe(d) g = torch.Generator().manual_seed(1) @@ -645,7 +645,7 @@ def capture(self, clip, lr, k): def test_routing_pins_record_and_force_experts(): """npu-lora check on MoE: the reference run can be pinned to another run's experts (moe.RoutingPins)""" - from npu_lora.moe import HostMLP, RoutingPins + from npu_train.moe import HostMLP, RoutingPins d = tempfile.mkdtemp() _tiny_moe(d) mlp = HostMLP(d)