Skip to content
SecurityOn
Go back

Completing WSL2 as a Practical Linux Research Environment

“Building an Offline Research Assets Foundation for Restricted-Network Research” ended with WSL_Research_Workflow=DEFERRED_TO_NOTE_7. WSL2 had already started Ubuntu, exposed /dev/dxg, reached the NVIDIA GPU, run PyTorch CUDA and a GPU benchmark, and connected to the matching VS Code Server. Those were valid results, but they proved a WSL GPU compute path rather than the independent Linux research environment intended by the original architecture.

During offline-asset preparation, ~/.vscode-server/extensions/extensions.json returned []. The server existed, but the remote Python and Jupyter extensions were missing. I recorded that incomplete state in Note 6 and left it for follow-up work.

This phase adds Linux development tools, isolated Python projects, Jupyter, VS Code Remote WSL, project-selected interpreters, GPU frameworks and shared models, then checks their operation after a reboot. Physical network isolation remains the separate end-to-end test for Note 8.

1. Defining the Environment Beyond GPU Access

The starting platform was Ubuntu 26.04.1 LTS (resolute) on kernel 6.18.33.2-microsoft-standard-WSL2, architecture x86_64, under user securityon. The Linux research root was /home/securityon/research; Windows research assets under D:\Lab were available as /mnt/d/Lab. Git was 2.53.0.

The GPU path exposed an NVIDIA GeForce RTX 5060 Laptop GPU with 8151 MiB VRAM through /dev/dxg. Windows reported KMD 616.92, while nvidia-smi in WSL reported 615.71.08 and CUDA UMD 13.4. These different strings are views within the WSL GPU architecture, not evidence of an independent Linux display driver. WSL continues to rely on the Windows NVIDIA driver path.

VS Code 1.138.0 and its WSL Server matched at commit:

7debcd0e2acdea1c52de81bf9ee1620444407dda

This supplied a sound platform boundary, but a complete research environment also required its own build tools, runtime separation, editor-side capabilities, project environments, and workflow-level validation.

2. Correcting a Misleading uv Inventory

My first inventory was launched indirectly from Windows PowerShell into wsl.exe bash. In that non-login, non-interactive shell, uv appeared to be missing. That conflicted with earlier WSL projects that had already used uv successfully.

An interactive WSL session showed the actual research-shell state:

PATH includes: /home/securityon/.local/bin
uv: /home/securityon/.local/bin/uv
uv version: 0.12.17

The first observation described that invocation’s PATH, not the installation state of the software. The PowerShell-to-bash inventory also encountered some quoting and CRLF friction, reinforcing that a diagnostic path can change what is observed.

When a shell cannot find a command, its mode, profile loading, environment inheritance and quoting need checking before drawing conclusions about installation.

3. Adding a Linux-native Build Toolchain

The corrected inventory found Git, Python, uv, GPU access, and VS Code Server, but no gcc, g++, make, cmake, ninja, pkg-config, or JupyterLab. Windows already had MSVC, CMake, the CUDA Toolkit, and Visual Studio Build Tools, but those tools do not make WSL an independent Linux development environment.

I installed and validated the native WSL stack:

ToolValidated version
GCC15.2.0
G++15.2.0
GNU Make4.4.1
CMake4.2.3
Ninja1.13.2
pkg-config2.5.1

The result was Linux_Native_Build_Toolchain=PASS. Linux projects now had their own compilers, headers, package metadata and filesystem on which to build.

4. Separating System Python from Research Python

Ubuntu’s system Python was 3.14.4 at /usr/bin/python3.14. I left it under distribution management. Research workloads instead use uv 0.12.17 (x86_64-unknown-linux-gnu) to manage Python 3.12.14, from the path family:

/home/securityon/.local/share/uv/python/cpython-3.12-linux-x86_64-gnu/

The runtime design is therefore explicit:

Ubuntu system Python 3.14.4
  -> operating-system and distribution-managed work

uv-managed research Python 3.12.14
  -> research project runtime

project-specific .venv
  -> dependency isolation

Replacing or downgrading Ubuntu’s Python would couple research compatibility to distribution internals. A separately managed research runtime lets projects use their validated Python version while the OS retains its own interpreter.

5. Creating the WSL Jupyter Research Base

I created /home/securityon/research/wsl-research-base as a dedicated base for common interactive research tooling. It uses uv-managed Python 3.12.14 in:

/home/securityon/research/wsl-research-base/.venv

The validated interactive stack was:

ComponentVersion
Python3.12.14
JupyterLab4.6.4
IPython9.17.1
ipykernel7.3.0

I opened the project through VS Code Remote WSL and selected /home/securityon/research/wsl-research-base/.venv/bin/python as the interpreter and kernel. A notebook reported Python 3.12.14, that exact executable, and:

Linux-6.18.33.2-microsoft-standard-WSL2-x86_64-with-glibc2.43

Running the remote kernel with the selected project interpreter produced Jupyter WSL kernel PASS.

6. Populating the WSL Remote Extension Layer

Before this phase, the WSL extension manifest had been empty. I populated the remote extension host and then inspected its actual final state:

WSL remote extensionVersion
ms-python.debugpy2026.6.0
ms-python.python2026.4.0
ms-python.vscode-pylance2026.3.1
ms-python.vscode-python-envs1.36.0
ms-toolsai.jupyter2025.9.1
ms-toolsai.jupyter-keymap1.1.2
ms-toolsai.jupyter-renderers1.3.0
ms-toolsai.vscode-jupyter-cell-tags0.1.9
ms-toolsai.vscode-jupyter-slideshow0.1.6

The final count was 9. Installing the Jupyter extension also brought related helper extensions into the remote host, so I do not describe all nine as individually installed by hand. The Remote - WSL extension itself remains on the Windows/local side and is not part of this remote count.

Following the earlier VS Code Server connection test, I recorded VS_Code_Remote_Extension_Layer=PASS.

7. Discovering the Workspace Trust Gate

After the Python extension existed in WSL, Python: Select Interpreter was initially unavailable. The extension UI showed Enable (Workspace), and the VS Code window was in Restricted Mode. The missing command was therefore not evidence of another failed installation.

After I trusted the research workspace, the Python extension became usable and the project interpreter could be selected. This exposed three independent states:

  1. extension files are installed;
  2. the extension is enabled for the workspace; and
  3. Workspace Trust permits its runtime features.

Even with an accurate extension inventory, editor security settings can restrict commands. After configuring trust, I recorded Workspace_Trust=PASS.

8. Revalidating the Existing PyTorch Project through VS Code

The existing /home/securityon/research/pytorch-smoke-test project already provided a WSL compute test. I reopened it through VS Code Remote WSL, now using the completed and trusted editor workflow, and selected:

/home/securityon/research/pytorch-smoke-test/.venv/bin/python

The project reported Python 3.12.14, PyTorch 2.14.0+cu132, bundled CUDA runtime 13.2, torch.cuda.is_available() as True, and the NVIDIA GeForce RTX 5060 Laptop GPU. A real 2048 x 2048 matrix multiplication completed with shape torch.Size([2048, 2048]) on cuda:0.

The result was VS Code WSL PyTorch CUDA PASS. The validated chain was now broader than the earlier compute test:

VS Code Remote WSL
  -> trusted workspace
  -> Python extension
  -> project-specific uv .venv
  -> PyTorch
  -> RTX 5060
  -> actual CUDA workload

These checks established PyTorch_CUDA_Research_Workflow=PASS.

9. Creating a WSL Transformers Project

The workstation already had a Windows transformers-smoke-test, but there was no corresponding WSL project. I created /home/securityon/research/transformers-smoke-test with Python 3.12 and the following declared dependencies:

accelerate==1.15.0
safetensors>=0.8.0
torch==2.14.0+cu132
torchvision==0.29.0+cu132
transformers==5.17.0

The PyTorch source remained explicit in project metadata:

[tool.uv.sources]
torch = { index = "pytorch" }
torchvision = { index = "pytorch" }

[[tool.uv.index]]
name = "pytorch"
url = "https://download.pytorch.org/whl/cu132"

This provenance cannot be collapsed into a generic PyPI dependency description. The dedicated cu132 index is part of what identifies the validated build. Imports of torch, transformers, accelerate, and safetensors passed; the environment reported PyTorch 2.14.0+cu132, Transformers 5.17.0, Accelerate 1.15.0, available CUDA, and the RTX 5060.

10. Reusing the Shared Windows Model Store

The existing Hugging Face store remained at D:\Lab\Models\HuggingFace, visible in WSL as /mnt/d/Lab/Models/HuggingFace. I intentionally did not duplicate Qwen/Qwen3-0.6B into the Linux filesystem. Validation used:

HF_HUB_CACHE=/mnt/d/Lab/Models/HuggingFace
HF_HUB_OFFLINE=1
TRANSFORMERS_OFFLINE=1
local_files_only=True

The tokenizer and model loaded from the local D: cache, all 311 / 311 weights loaded, and inference completed on cuda:0. The resulting path was:

D:\Lab\Models\HuggingFace
  -> /mnt/d/Lab/Models/HuggingFace
  -> WSL Python 3.12
  -> Transformers 5.17.0
  -> PyTorch 2.14.0+cu132
  -> RTX 5060 cuda:0

The final result was WSL Transformers offline CUDA inference PASS and Shared_Windows_WSL_Model_Store=PASS. Sharing the model root avoids maintaining separate multi-gigabyte Windows and WSL copies.

This was an offline-mode validation: library-level network lookup was disabled and local assets were required. It was not a physically disconnected test, which remains part of Note 8.

11. Recording the Triton Warning without Overstating It

During WSL Transformers inference, Triton emitted a _POSIX_C_SOURCE redefined warning while compiling NVIDIA-related helper code. After the warning, the model still loaded, the GPU remained available, inference completed, cuda:0 was used, and the final PASS was printed.

I therefore recorded:

Triton_Compile_Warning=OBSERVED
Functional_Impact=None observed

A header or macro redefinition warning occurred, but the test succeeded. I did not establish a deeper cause or observe a functional impact.

12. Validating the Project-specific Interpreter Path

Earlier, the actual .ipynb and kernel path in wsl-research-base had established the general Jupyter Research Workflow. The project-specific Transformers evidence came instead from the selected project .venv in a Python interactive environment, not from a separately demonstrated notebook kernel session.

wsl-research-base
  -> validated .ipynb and kernel workflow

project-specific uv environment in transformers-smoke-test
  -> selected interpreter and actual project dependencies

Through the selected project .venv and Python interactive environment, the Transformers project reported PyTorch 2.14.0+cu132, Transformers 5.17.0, CUDA True, and the NVIDIA GeForce RTX 5060 Laptop GPU.

This established Project_Specific_Venv=PASS and Project_Python_CUDA=PASS. The general Jupyter Research Workflow remains PASS on the basis of the actual .ipynb and kernel validation in wsl-research-base recorded in Section 5.

13. Keeping Stable TensorFlow Results Deferred

TensorFlow remained a secondary WSL framework path, not a gate for completing the primary research environment. I retained the stable-version evidence from the earlier investigation:

VersionInstallationGPU discoveryGPU execution
TensorFlow 2.21.0PASSFAILNot reached
TensorFlow 2.20.0PASSPASSFAIL

For 2.21.0, NVIDIA library and ptxas remediation had been applied, but GPU discovery still failed. For 2.20.0, the RTX 5060 appeared with Compute Capability 12.0, but actual execution failed first with CUDA_ERROR_INVALID_PTX and then CUDA_ERROR_INVALID_HANDLE.

The stable result remains Stable_TensorFlow_GPU_Path=DEFERRED. I retained separate results for installation, GPU discovery and execution.

14. Demonstrating a TensorFlow Nightly Preview Path

I also tested the TensorFlow 2.22.0-dev20260923 nightly development build (tf-nightly). It produced a materially different result: the RTX 5060 was discovered, Compute Capability 12.0a was reported, and a normal 2048 x 2048 GPU matrix multiplication passed.

The first successful run made several large CUDA allocation attempts and emitted out-of-memory warnings, although the workload itself still completed. I then enabled memory growth. The same 2048 x 2048 matrix multiplication passed again without the earlier large pre-allocation warnings.

For a second gate, I ran a 1024 x 1024 matrix multiplication under @tf.function(jit_compile=True). The XLA service initialised for CUDA, reported the RTX 5060 at Compute Capability 12.0a, loaded cuDNN 9.26.0, and logged:

Compiled cluster using XLA!

The XLA workload completed with TensorFlow Nightly XLA GPU PASS. An AutoGraph warning also appeared because the function had been defined through interactive or standard input and its source could not be located; it had no observed functional impact on this run.

The correct classification is TensorFlow_Nightly_GPU_Path=PREVIEW_PASS. A nightly development build can demonstrate a plausible future-compatible path, but it is not a stable, beta, or production baseline. I will retest TensorFlow 2.22 after a stable release before adopting it as the stable GPU path.

15. Verifying Project-managed cuDNN

I also checked how cuDNN was installed. No cuDNN system package was installed inside WSL: dpkg -l | grep -i cudnn returned no result. That did not mean cuDNN was unavailable to TensorFlow. The environment at /home/securityon/research/tensorflow-nightly-smoke-test contained nvidia-cudnn-cu12 9.26.0.51, and XLA execution logged Loaded cuDNN version 92600.

The observed state was:

System_Wide_cuDNN=NOT_INSTALLED
TensorFlow_Project_cuDNN=9.26.0.51
cuDNN_Runtime_Use=VERIFIED

This matches the runtime architecture: Ubuntu owns system components, while each framework project owns its Python and GPU-library dependency set. A system package query and a project runtime inventory answer different questions.

16. Revalidating after a Windows Reboot

After completing the environment, I rebooted Windows and revalidated WSL startup, the Ubuntu release and kernel, the Linux build toolchain, uv, research Python, the Jupyter base, /dev/dxg, RTX 5060 access, the matching VS Code Server, the nine remote extensions, the PyTorch CUDA workflow, and the Transformers local-model workflow.

All checks passed after reboot, producing Reboot_Persistence=PASS.

17. Recording the WSL Research Baseline

I recorded the baseline after completing the WSL research environment and reboot-persistence validation in:

D:\Lab\OfflineLab\manifests\wsl-research-baseline.txt

Its WSL path is:

/mnt/d/Lab/OfflineLab/manifests/wsl-research-baseline.txt

At the time of recording, D:\Lab\OfflineLab\manifests contained 21 files. The Note 6 inventory was captured earlier, so I retained each count at its original point in the record.

This manifest records the WSL research baseline and the stable TensorFlow DEFERRED state at that time. The TensorFlow nightly result was later follow-up evidence included in this Note; I did not retroactively rewrite the historical baseline manifest to include it.

18. Reviewing the Completion Gates

The Note 7 final state includes the later TensorFlow nightly follow-up while separating the completed primary environment from the remaining stable TensorFlow limitation:

GateResult
Ubuntu WSL2PASS
Linux Native Build ToolchainPASS
Research Python RuntimePASS
Jupyter Research WorkflowPASS
VS Code Remote WSLPASS
VS Code Remote Extension LayerPASS
Workspace TrustPASS
Project-specific .venvPASS
Project Python CUDAPASS
PyTorch CUDA Research WorkflowPASS
Transformers CUDA Research WorkflowPASS
Shared Model StorePASS
Reboot PersistencePASS
Stable TensorFlow GPU PathDEFERRED
TensorFlow Nightly GPU PathPREVIEW_PASS
WSL2 Research EnvironmentPASS

The primary WSL environment passed its Linux build, Python, Jupyter, PyTorch, Transformers, model-sharing and editor checks. Stable TensorFlow remains deferred, while nightly results are retained separately as follow-up compatibility evidence.

19. From Compute Path to Research Environment

WSL2 has progressed from a validated GPU compute path to a usable Linux research environment. It now has Linux-native build tools, isolated research Python, Jupyter, a populated VS Code remote extension layer, trusted research workspaces, project-specific uv environments, PyTorch CUDA, Transformers CUDA, shared local-model access, and reboot persistence.

This work checked shell invocation, remote extensions, trust settings and project-library locations against actual execution. The record retains distinctions that an installation inventory alone could miss.

The correct closing status is:

WSL2 Research Environment = PASS
Stable TensorFlow GPU Path = DEFERRED
TensorFlow Nightly GPU Path = PREVIEW PASS

The tf-nightly 2.22.0-dev20260923 result demonstrates RTX 5060 discovery, normal and memory-growth GPU execution, XLA/JIT execution, and project-managed cuDNN 9.26 use. It remains preview evidence, not the stable baseline.

The complete workstation has not yet undergone physically disconnected validation. With the WSL gaps identified in Note 6 addressed, Note 8 will test the full environment offline.


Share LinkedIn

Previous Note
Building an Offline Research Assets Foundation for Restricted-Network Research
Next Note
Validating the Research Environment Under Physical Network Isolation