使用Pixi构建Python环境

7 阅读8分钟

1.Pixi的linux安装

  一、Linux 安装 Pixi(推荐)

  • 一键安装脚本(官方)

    • curl -fsSL `[`https://pixi.sh/install.sh`](https://pixi.sh/install.sh)` | bash
      
    • 以下是可选项:

      • !指定Pixi安装路径(默认安装路径~/.pixi):
        需要在执行安装脚本之前

        # 指定安装路径
        export PIXI_HOME=/data/xds/Worktools/pixi
        # 指定家目录的
        export PATH_HOME="$HOME/your_path"
        curl -fsSL https://pixi.sh/install.sh | bash
        
      • 将pixi安装路径加到 PATH(若安装脚本未自动添加):

        export PIXI_HOME="$HOME/Worktools/pixi"
        export PATH="$PIXI_HOME/bin:$PATH"
        
  • 验证

    • pixi --version 查看版本
    • which pixi 查看安装位置

  ‍

2.Pixi的环境构建

2.1 全局的镜像环境构建

  • 使用prepend加载前面,append加载后面,--global参数代表全局的镜像

    # 1. 设置默认通道(清华镜像 + NVIDIA)
    pixi config set default-channels '[
      "https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/conda-forge",
      "https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/bioconda",
      "https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/pytorch",
      "nvidia"
    ]'
    
    # 2. 设置 PyPI 清华镜像
    pixi config set pypi-config.index-url "https://pypi.tuna.tsinghua.edu.cn/simple"
    
    # 3. 验证配置
    pixi config list
    
  • 使用list查看目前的config设置

    pixi config list --global
    
  • 目前对于pixi来说,只有一个模式,就是识别conda-forge,bioconda等关键字,不会识别是否是镜像源 ,所以不能在环境中存在两个相同的通道会报错.
    同时,之前在conda中的镜像环境太冗杂,经过精简后,以下环境应该可以满足大部分需求.

    default-channels = [
        "https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/conda-forge",
        "https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/bioconda",
        "https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/pytorch",
        "nvidia"
    ]
    
    [pypi-config]
    index-url = "https://pypi.tuna.tsinghua.edu.cn/simple"
    

  ‍

2.2 全局的环境构建

1. 查看pixi中全局的环境:

pixi global list
# 或者简写
pixi g list

2.创建全局环境中不同的python环境类似于conda:

  使用install命令来创建新的环境,注意参数:--environment 和--expose 选择不暴露,这样就不会冲突

# 创建 Python 3.10 环境(不指定 --expose 来测试默认行为)
pixi global install --environment scanpy \
  --expose "py310_scanpy=python" \
  python=3.10 pip numpy pandas scanpy
# 创建 Python 3.11 环境
pixi global install --environment scgpt \
  --expose "py311_scgpt=python" \
  python=3.11 jupyter
自定义命令名称示例
--expose 语法说明
--expose <EXPOSE>
Add one or more mapping which describe which executables are exposed. 
The syntax is `exposed_name=executable_name`, so for example `python3.10=python`. 
Alternatively, you can input only an executable_name and `executable_name=executable_name` is assumed
方法一:自定义别名
# 将 python 暴露为 py311,将 pip 暴露为 pip311
pixi global install --environment python311 \
  --expose "py311=python" \
  --expose "pip311=pip" \
  --expose "jupyter311=jupyter" \
  python=3.11 pip jupyter
方法二:功能性命名
# 给命令起有意义的名字
pixi global install --environment python311 \
  --expose "python-data=python" \
  --expose "pip-data=pip" \
  --expose "notebook=jupyter" \
  python=3.11 pip jupyter matplotlib pandas
方法三:版本化命名
# 使用版本号避免冲突
pixi global install --environment python311 \
  --expose "python3.11=python" \
  --expose "pip3.11=pip" \
  --expose "jupyter3.11=jupyter" \
  python=3.11 pip jupyter

3.如果修改了pixi-global.toml

# 按照警告提示,先同步环境
pixi global sync

  同步环境的注意事项:

version = 1

[envs.r]
channels = ["https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/conda-forge", "https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/bioconda", "https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/pytorch", "https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main", "https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free", "https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/r", "nvidia"]
dependencies = { r-base = "*", r-essentials = "*", r-pak = "*", pkg-config = "*", freetype = "*", fontconfig = "*", harfbuzz = "*", fribidi = "*", libpng = "*", libjpeg-turbo = "*", libtiff = "*", glpk = "*", libgit2 = "*", pandoc = "*", imagemagick = "*", perl = "*", libarrow = "*", arrow-cpp = "*", r-arrow = "*", r-sf = "*", r-spdep = "*", gdal = "*", proj = "*", geos = "*", udunits2 = "*" }
exposed = { R = "R", Rscript = "Rscript" }

[envs.scanpy]
channels = ["https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/conda-forge", "https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/bioconda", "https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/pytorch", "https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main", "https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free", "https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/r", "nvidia"]
dependencies = { python = "3.10.*", pip = "*", numpy = "*", pandas = "*", scanpy = "*" }
exposed = { py310_scanpy = "python", pip310_scanpy = "pip" }

4.使用环境

  这里的使用环境和mamba不同,全局环境是直接存在的,不需要进入shell
想要使用只能使用expose暴露的虚拟环境中的接口调用对应环境中的可执行文件。

pixi g add

  可以使用pixi g add​添加包到具体的环境中,但是值得注意的是,这里pixi g add​没有具体到项目中的pixi add​好用,例如没有--pypi参数等。

# 添加 scanpy 包
pixi g add --environment scanpy scanpy

  但其实,就这些就已经相当于是mamba的同类型替代,更何况既快速又拥有其它功能。

  ‍

2.3 项目级别的环境构建

1. pixi init​初始化或者直接复制配置好的pixi.toml​和pixi.lock到环境中

  • pixi init初始化环境,在空白的项目中添加pixi.toml配置文件。

  • 直接复制配置好的pixi.toml​和pixi.lock​到环境中
    然后直接使用pixi install命令就可以复刻环境

2. pixi add在项目中添加包

[!NOTE] 注意这里的命令必须在项目的根目录及子目录下使用,才是在项目中添加包,与pixi global install是在全局环境中添加工具包做区分。

  • 直接使用pixi add [packagename]是通过与conda/mamba相同的方式检索现有的频道中的编译好的包直接安装,但是更快!
  • 如果使用pixi add --pypi [packagename] 是通过pixi现在内置的uv来进行pip途径可以安装的包的管理。这两种方式都会写入pixi.toml。

3.通过task的方式在pixi.toml中借用pip来安装包

[!NOTE] 由于现在可能pixi的pypi方式不够完善,之前我配置scgpt环境的时候,有的包通过pypi途径无法下载,或者自动解析会下载cpu版本,例如pytorch。而python中的pip命令,目前可以使用更加详尽的参数来控制,例如指定某个镜像包。所以需要使用在pixi.toml​中写入task的方式来安装包。

[tasks]
# 一键安装命令
# 这里depends-on的意思就是运行此命令会先自动运行这些依赖的命令
setup-all = { depends-on = ["install-torch", "install-flash-attn", "check-env"] }

# 🚀 [迁移核心]:在此处统一修改系统 CUDA 路径
# 理由:编译需要系统 NVCC,运行 JAX 需要系统驱动库。pixi环境中的 CUDA 像是零件包,无法满足需求。
_system_cuda = "export CUDA_HOME=/usr/local/cuda-12.4 && export LD_LIBRARY_PATH=$CUDA_HOME/lib64:$LD_LIBRARY_PATH"

# 1. 安装支持 CUDA 12.1 的 PyTorch
install-torch = "pip install torch==2.3.0 torchvision==0.18.0 torchaudio==2.3.0 --index-url https://mirror.nju.edu.cn/pytorch/whl/cu121"

# 2. 现场编译 Flash Attention (会自动引用 _setup_cuda 中的路径)
# 💡 迁移提示:修改 _setup_cuda 任务中的路径即可全局生效
install-flash-attn = { cmd = "pip install flash-attn==1.0.4 --no-build-isolation --no-cache-dir", depends-on = ["_system_cuda"] }

# 3. 环境全项验证
check-env = { cmd = "python -c 'import jax; import torch; print(f\"PyTorch GPU: {torch.cuda.is_available()}\"); print(f\"JAX Devices: {jax.devices()}\")'", depends-on = ["_system_cuda"] }

# 4. 使用python脚本全项验证
test-scgpt = { cmd = "python test_scgpt.py", depends-on = ["_system_cuda"] }

  调用的task的命令:

  • pixi run [taskname]​例如调用上方的task就是pixi run setup-all
    这里的pixi run​可以运行pixi中环境中系统命令。如:如果要运行pixi环境中的python就使用pixi run python​,可以通过查看python版本号等方式来验证是否正确,pixi run python --version
    同时这里的pixi run命令必须在项目的根目录或者子目录下运行。

    ➜  scGPT-demo pixi run python --version 
    Python 3.10.19
    ➜  scGPT-demo 
    

2.4 scgpt项目配置文件示例

1. pixi.toml

[!NOTE] 虽然pixi的可迁移性非常高,而且速度非常快,但是在该项目中,由于scgpt只兼容flash-attn<1.0.4版本过于老旧,所以基本上只能靠编译
而编译则与系统的cuda,cudnn,python的版本,pytorch等的版本息息相关。在配置过程中还发现有jaxlib的版本也相关。注意这里是系统的,而不是pixi环境中的,pixi环境中的不具备编译功能,所以在任务中需要输入系统的cuda的环境变量和版本号,还需要配齐对应的pytorch等。

所以这里需要注意你的linux系统中的cuda和cudnn的版本,然后才能成功编译flash-attn。

[!WARNING] 这里是使用scgpt的环境,不是开发本地scgpt的环境,所以安装scgpt的包。

[workspace]
name = "scGPT"
version = "0.1.0"
authors = ["kleikli <x1246050927@gmail.com>"]
channels = [
    "https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/conda-forge",
    "https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/bioconda",
    "https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/pytorch",
    "nvidia"
]
platforms = ["linux-64"]

[pypi-options]
index-url = "https://pypi.tuna.tsinghua.edu.cn/simple"

[dependencies]
python = "3.11.*"
pip = ">=25.2,<26"
gcc_linux-64 = "12.*"
gxx_linux-64 = "12.*"
ninja = "*"
pyarrow = "<15.0"
jupyter = ">=1.1.1,<2"
ipykernel = ">=6.0,<7"

[pypi-dependencies]
torchvision = "==0.18.0"
torchaudio = "==2.3.0"
packaging = ">=25.0, <26"
ipykernel = ">=6.30.1, <7"
jax = "==0.4.13"
jaxlib = { url = "https://storage.googleapis.com/jax-releases/cuda12/jaxlib-0.4.13%2Bcuda12.cudnn89-cp311-cp311-manylinux2014_x86_64.whl" }
scgpt = ">=0.2.4, <0.3"
scvi-tools = "==0.20.3"
anndata = "==0.9.2"
scanpy = ">=1.10.0"
scipy = "<1.13.0"
wandb = ">=0.22.2, <0.23"
gseapy = ">=1.1.10, <2"
faiss-cpu = ">=1.12.0, <2"
gpustat = ">=1.1.1, <2"

[tasks]
# 一键安装命令
setup-all = { depends-on = ["install-torch", "install-flash-attn", "check-env"] }

# 🚀 [迁移核心]:在此处统一修改系统 CUDA 路径
# 理由:编译需要系统 NVCC,运行 JAX 需要系统驱动库。pixi环境中的 CUDA 像是零件包,无法满足需求。
_system_cuda = "export CUDA_HOME=/usr/local/cuda-12.4 && export LD_LIBRARY_PATH=$CUDA_HOME/lib64:$LD_LIBRARY_PATH"

# 1. 安装支持 CUDA 12.1 的 PyTorch
install-torch = "pip install torch==2.3.0 torchvision==0.18.0 torchaudio==2.3.0 --index-url https://mirror.nju.edu.cn/pytorch/whl/cu121"

# 2. 现场编译 Flash Attention (会自动引用 _setup_cuda 中的路径)
# 💡 迁移提示:修改 _setup_cuda 任务中的路径即可全局生效
install-flash-attn = { cmd = "pip install flash-attn==1.0.4 --no-build-isolation --no-cache-dir", depends-on = ["_system_cuda"] }

# 3. 环境全项验证
check-env = { cmd = "python -c 'import jax; import torch; print(f\"PyTorch GPU: {torch.cuda.is_available()}\"); print(f\"JAX Devices: {jax.devices()}\")'", depends-on = ["_system_cuda"] }

# 4. 使用python脚本全项验证
test-scgpt = { cmd = "python test_scgpt.py", depends-on = ["_system_cuda"] 

2. test_scgpt.py

[!NOTE] 这里是使用ai顺手写的测试脚本,注意,这是使用scgpt的环境的脚本,不是开发scgpt,如果要开发scgpt,则不需要安装scgpt库,而是在上面的pixi.toml中将scgpt本地的包安装入环境中。

import os
import sys
import warnings
import torch
import numpy as np

# ==========================================
# 1. 警告与日志过滤 (保持终端整洁)
# ==========================================
os.environ["TF_CPP_MIN_LOG_LEVEL"] = "3" 
try:
    import torchtext
    torchtext.disable_torchtext_deprecation_warning()
except (ImportError, AttributeError):
    pass

warnings.filterwarnings("ignore", message=".*The 'frozen' attribute with value True.*")
warnings.filterwarnings("ignore", message=".*enable_nested_tensor is True, but self.use_nested_tensor is False.*")
warnings.filterwarnings("ignore", category=UserWarning, module="lightning_fabric")

# 针对 RTX 4090 的优化设置
if torch.cuda.is_available():
    torch.set_float32_matmul_precision('high')

def test_environment():
    print(f"{'='*20} 1. 环境基础检查 {'='*20}")
    print(f"Python 路径: {sys.executable}")
    print(f"PyTorch 版本: {torch.__version__}")
    print(f"CUDA 是否可用: {torch.cuda.is_available()}")
    if torch.cuda.is_available():
        print(f"当前 GPU: {torch.cuda.get_device_name(0)}")

    # ==========================================
    # 2. JAX GPU 加速检测
    # ==========================================
    print(f"\n{'='*20} 2. JAX 后端检测 {'='*20}")
    try:
        import jax
        devices = jax.devices()
        print(f"✅ JAX 加载成功! 版本: {jax.__version__}")
        print(f"✅ JAX 识别设备: {devices}")
        if "gpu" not in str(devices[0]).lower():
            print("⚠️ 警告: JAX 未识别到 GPU,请检查 jaxlib 版本。")
    except Exception as e:
        print(f"❌ JAX 检测失败: {e}")

    # ==========================================
    # 3. 单细胞核心算法库加载
    # ==========================================
    print(f"\n{'='*20} 3. 核心算法库加载 {'='*20}")
    try:
        import scvi
        import scanpy as sc
        import scgpt
        print(f"✅ scvi-tools 版本: {scvi.__version__}")
        print(f"✅ scanpy 版本: {sc.__version__}")
        print(f"✅ scGPT 模块路径: {scgpt.__path__[0]}")
    except Exception as e:
        print(f"❌ 库加载失败: {e}")

    # ==========================================
    # 4. Flash Attention 与 scGPT 模型验证
    # ==========================================
    print(f"\n{'='*20} 4. Flash Attention 算子验证 {'='*20}")
    try:
        import flash_attn
        print(f"✅ Flash Attention 库已安装 (版本: {getattr(flash_attn, '__version__', '1.0.4')})")
        
        from scgpt.model import TransformerModel
        
        # 构造模拟词表 (ntoken=100)
        fake_vocab = {"<pad>": 0, "<mask>": 1, "<cls>": 2}
        for i in range(3, 100):
            fake_vocab[f"gene_{i}"] = i
            
        # 初始化模型
        model = TransformerModel(
            ntoken=100, 
            d_model=128, 
            nhead=4, 
            d_hid=128, 
            nlayers=2,
            vocab=fake_vocab,
            pad_token="<pad>",
            use_fast_transformer=True, 
            fast_transformer_backend="flash"
        ).cuda()

        # RTX 4090 性能优化:使用半精度进行前向传播测试
        model.half()
        
        # 准备模拟数据 (Batch=2, Seq_len=10)
        src = torch.randint(1, 100, (2, 10)).cuda()
        values = torch.randn(2, 10).cuda().half()
        # 关键修复:构造 src_key_padding_mask
        # 0 代表该位置是真实的基因,1 代表是填充位
        src_key_padding_mask = torch.zeros(2, 10, dtype=torch.bool).cuda()

        with torch.no_grad():
            # 执行前向传播
            output = model(
                src, 
                values, 
                src_key_padding_mask=src_key_padding_mask
            )
        
        print("✅ scGPT 模型初始化成功并已载入 GPU")
        print(f"✅ 前向传播测试成功!")
        print(f"✅ 细胞嵌入 (Cell Embedding) 形状: {output['cell_emb'].shape}")
        print("\n🚀 结论:您的 scGPT 环境已达满血状态,Flash Attention 运行正常!")

    except Exception as e:
        print(f"❌ 模型验证环节出错: {e}")
        print("💡 建议:检查 scgpt 源码版本是否与参数匹配。")

    print(f"\n{'='*50}")
    print("🌟 所有测试已完成!")
    print(f"{'='*50}")

if __name__ == "__main__":
    test_environment()

  ‍

  ‍