LocLLM is a permutation-invariant lane detection system that fuses a DINOv3 vision encoder with a Qwen large language model backbone. A DETR-style decoder head predicts lane keypoints as normalized coordinates, enabling end-to-end training without relying on fixed lane ordering.
Key design choices:
[0, 1] using original CULane dimensions (1640 × 590 px) as the single source of truth.DINOv3Wrapper that clamps, NaN-replaces, and LayerNorms features before they reach the LLM.| Item | Details |
|---|---|
| GPU | NVIDIA RTX 5000 (16 GB VRAM) |
| Training Platform | Run:ai |
| Framework | PyTorch |
| Mixed Precision | Float32 (configurable) |
| Quantization | QLoRA 4-bit (optional) |
Lane keypoints are predicted at 43 fixed Y positions (CULane's h_samples):
h_samples = list(range(160, 590, 10)) # y = 160, 170, …, 580
Only the X coordinate is learned; Y is always fixed and known.
LocLLM/
├── datasets/
│ ├── __init__.py
│ ├── constants.py # Keypoint name/description tables
│ ├── conversation.py # Chat templates (Simple, Qwen, LLaMA-2)
│ └── culane.py # CULane dataset + CoordinateConverter + TokenizingCollator
│
├── models/
│ ├── __init__.py
│ ├── locllm.py # Main model: LocLLMKeypointModel + DETRLaneDetector
│ ├── cross_attention.py # GatedCrossAttention + CrossAttentionInjector
│ ├── dino.py # DinoVisionTransformer (ViT-B/16 with RoPE)
│ ├── lora.py # LoRA adapters for DINOv3 / LLaMA / CLIP
│ └── dino_layers/ # Building blocks: attention, block, mlp, rope, …
│
├── utils/
│ ├── train2d.py # Training script (Trainer class)
│ ├── test2d.py # Evaluation script (CULaneEvaluator, F1/Precision/Recall)
│ ├── inference.py # Inference + visualization (LaneInference)
│ ├── evaluate_model.py # Thin wrapper around test2d for quick eval
│ ├── culane_metric.py # Official CULane IoU metric (discrete/continuous)
│ ├── diagnose_eval.py # Per-sample evaluation diagnosis tool
│ ├── diagnose_vision.py # Vision feature diversity checker
│ └── distributed/ # Distributed training helpers
│
├── scripts/
│ ├── train_culane.sh # End-to-end training launcher
│ └── test_culane.sh # Evaluation launcher (all 9 CULane categories)
│
└── debug_coordinates.py # Standalone coordinate space debug script
# 1. Clone the repository
git clone <repo_url>
cd LocLLM
# 2. Create environment
conda create -n locllm python=3.10 -y
conda activate locllm
# 3. Install PyTorch (adjust CUDA version as needed)
pip install torch torchvision --index-url https://download.pytorch.org/whl/cu124
# 4. Install dependencies
pip install transformers peft accelerate bitsandbytes
pip install scipy opencv-python Pillow shapely
pip install tqdm tensorboard
Download CULane and organise as:
culane_dataset/
├── driver_23_30frame/
├── driver_100_30frame/
├── driver_161_90frame/
├── driver_182_30frame/
├── driver_193_90frame/
└── list/
├── train_gt.txt
├── val_gt.txt
└── test_gt.txt
Each annotation .lines.txt file contains one lane per line with space-separated x y pairs in pixel coordinates.
| Component | Source |
|---|---|
| DINOv3 ViT-B/16 | dinov3_vitb16_pretrain_lvd1689m.pth |
| Qwen LLM | Qwen/Qwen3-1.7B-Instruct (HuggingFace) |
Place DINOv3 weights at:
checkpoints/model_weights/dinov3_vitb16_pretrain_lvd1689m.pth
bash scripts/train_culane.sh
python utils/train2d.py \
--llm_model /path/to/Qwen3-1.7B-Instruct \
--vision_model_path /path/to/dinov3_vitb16_pretrain_lvd1689m.pth \
--data_root /path/to/culane_dataset \
--output_dir ./checkpoints/culane \
--hidden_dim 2048 \
--max_lanes 4 \
--num_points_per_lane 43 \
--num_queries 8 \
--num_decoder_layers 6 \
--use_lora \
--lora_r 8 \
--lora_alpha 16 \
--use_float32 \
--no_lm_loss \
--batch_size 2 \
--gradient_accumulation_steps 8 \
--num_epochs 10 \
--learning_rate 3e-4 \
--freeze_vision_encoder \
--shuffle_gt_lanes \
--loss_weight_class 2.0 \
--loss_weight_discrete 5.0 \
--loss_weight_bezier 3.0
| Flag | Default | Description |
|---|---|---|
--use_float32 | False | Full float32 training (recommended for RTX 5000) |
--use_qlora | False | 4-bit quantization of the LLM backbone |
--no_lm_loss | False | Skip language modelling loss (~600 MB saved) |
--shuffle_gt_lanes | True | Permutation-invariant GT shuffling |
--freeze_vision_encoder | True | Freeze DINOv3 (only wrapper norm trainable) |
| Component | Approx. VRAM |
|---|---|
| Qwen 1.7B + LoRA (float32) | ~7 GB |
| DINOv3 ViT-B/16 (float32, frozen) | ~1 GB |
| DETR head + mm_projector | ~0.3 GB |
| Gradients + optimizer states | ~6 GB |
| Peak (batch=2, grad_accum=8) | ~15 GB |
python utils/test2d.py \
--checkpoint ./checkpoints/culane/best \
--data_root /path/to/culane_dataset \
--output_dir ./eval_results \
--split test \
--batch_size 4 \
--confidence_threshold 0.5 \
--iou_threshold 0.5
bash scripts/test_culane.sh ./checkpoints/culane/best
Categories: normal, crowd, hlight, shadow, noline, arrow, curve, cross, night.
The evaluator follows the official CULane protocol:
0.5.python utils/inference.py \
--checkpoint_path ./checkpoints/culane/best \
--image_folder /path/to/culane_dataset \
--test_list /path/to/culane_dataset/list/test_gt.txt \
--output_dir ./visualizations \
--num_samples 100 \
--iou_threshold 0.5
Green polylines = ground truth lanes. Red polylines = model predictions.
All coordinates flow through a single CoordinateConverter instance.
| Space | Range | Used For |
|---|---|---|
original | [0, 1640] × [0, 590] px | CULane raw annotations |
normalized | [0, 1] × [0, 1] | Model training & inference |
resized | [0, 608] × [0, 224] px | Vision encoder input |
Rule: GT annotations are parsed in original space, out-of-bounds points are dropped, then everything is normalized before being fed to the model.
| Loss | Weight | Description |
|---|---|---|
class_loss | 2.0 | Binary cross-entropy: lane vs. no-lane |
discrete_loss | 5.0 | L1 on keypoints at GT-valid Y positions + adaptive continuity |
bezier_loss | 3.0 | L1 on Bézier control points |
direction_loss | 1.0 | Cosine similarity of start/end tangent vectors |
smooth_loss | 0.5 | Second-order smoothness on Bézier curves |
lm_loss | 0.1 | Cross-entropy on language modelling tokens (optional) |
# Check coordinate spaces for a single image
python debug_coordinates.py
# Evaluate prediction quality in detail (IoU per lane, template collapse detection)
python utils/diagnose_eval.py \
--checkpoint ./checkpoints/culane/best \
--data_root /path/to/culane_dataset \
--output_dir ./diagnosis \
--num_samples 30
# Verify vision features are diverse across images
python utils/diagnose_vision.py \
--checkpoint_path ./checkpoints/culane/best \
--image_folder /path/to/culane_dataset \
--test_list /path/to/culane_dataset/list/test_gt.txt \
--num_images 5
TODO
Handle illusions — improve robustness to optical illusions and ambiguous lane markings (e.g. shadow patterns, road artifacts, and painted markings that visually resemble lane lines but are not). This may require dedicated augmentation strategies, hard-negative mining, or a post-processing rejection module.
This codebase builds upon:
MIT License — see LICENSE for full terms.
463 commits
Python
96.2%
Shell
3.8%
LocLLM is a permutation-invariant lane detection system that fuses a DINOv3 vision encoder with a Qwen large language model backbone. A DETR-style decoder head predicts lane keypoints as normalized coordinates, enabling end-to-end training without relying on fixed lane ordering.
Key design choices:
[0, 1] using original CULane dimensions (1640 × 590 px) as the single source of truth.DINOv3Wrapper that clamps, NaN-replaces, and LayerNorms features before they reach the LLM.| Item | Details |
|---|---|
| GPU | NVIDIA RTX 5000 (16 GB VRAM) |
| Training Platform | Run:ai |
| Framework | PyTorch |
| Mixed Precision | Float32 (configurable) |
| Quantization | QLoRA 4-bit (optional) |
Lane keypoints are predicted at 43 fixed Y positions (CULane's h_samples):
h_samples = list(range(160, 590, 10)) # y = 160, 170, …, 580
Only the X coordinate is learned; Y is always fixed and known.
LocLLM/
├── datasets/
│ ├── __init__.py
│ ├── constants.py # Keypoint name/description tables
│ ├── conversation.py # Chat templates (Simple, Qwen, LLaMA-2)
│ └── culane.py # CULane dataset + CoordinateConverter + TokenizingCollator
│
├── models/
│ ├── __init__.py
│ ├── locllm.py # Main model: LocLLMKeypointModel + DETRLaneDetector
│ ├── cross_attention.py # GatedCrossAttention + CrossAttentionInjector
│ ├── dino.py # DinoVisionTransformer (ViT-B/16 with RoPE)
│ ├── lora.py # LoRA adapters for DINOv3 / LLaMA / CLIP
│ └── dino_layers/ # Building blocks: attention, block, mlp, rope, …
│
├── utils/
│ ├── train2d.py # Training script (Trainer class)
│ ├── test2d.py # Evaluation script (CULaneEvaluator, F1/Precision/Recall)
│ ├── inference.py # Inference + visualization (LaneInference)
│ ├── evaluate_model.py # Thin wrapper around test2d for quick eval
│ ├── culane_metric.py # Official CULane IoU metric (discrete/continuous)
│ ├── diagnose_eval.py # Per-sample evaluation diagnosis tool
│ ├── diagnose_vision.py # Vision feature diversity checker
│ └── distributed/ # Distributed training helpers
│
├── scripts/
│ ├── train_culane.sh # End-to-end training launcher
│ └── test_culane.sh # Evaluation launcher (all 9 CULane categories)
│
└── debug_coordinates.py # Standalone coordinate space debug script
# 1. Clone the repository
git clone <repo_url>
cd LocLLM
# 2. Create environment
conda create -n locllm python=3.10 -y
conda activate locllm
# 3. Install PyTorch (adjust CUDA version as needed)
pip install torch torchvision --index-url https://download.pytorch.org/whl/cu124
# 4. Install dependencies
pip install transformers peft accelerate bitsandbytes
pip install scipy opencv-python Pillow shapely
pip install tqdm tensorboard
Download CULane and organise as:
culane_dataset/
├── driver_23_30frame/
├── driver_100_30frame/
├── driver_161_90frame/
├── driver_182_30frame/
├── driver_193_90frame/
└── list/
├── train_gt.txt
├── val_gt.txt
└── test_gt.txt
Each annotation .lines.txt file contains one lane per line with space-separated x y pairs in pixel coordinates.
| Component | Source |
|---|---|
| DINOv3 ViT-B/16 | dinov3_vitb16_pretrain_lvd1689m.pth |
| Qwen LLM | Qwen/Qwen3-1.7B-Instruct (HuggingFace) |
Place DINOv3 weights at:
checkpoints/model_weights/dinov3_vitb16_pretrain_lvd1689m.pth
bash scripts/train_culane.sh
python utils/train2d.py \
--llm_model /path/to/Qwen3-1.7B-Instruct \
--vision_model_path /path/to/dinov3_vitb16_pretrain_lvd1689m.pth \
--data_root /path/to/culane_dataset \
--output_dir ./checkpoints/culane \
--hidden_dim 2048 \
--max_lanes 4 \
--num_points_per_lane 43 \
--num_queries 8 \
--num_decoder_layers 6 \
--use_lora \
--lora_r 8 \
--lora_alpha 16 \
--use_float32 \
--no_lm_loss \
--batch_size 2 \
--gradient_accumulation_steps 8 \
--num_epochs 10 \
--learning_rate 3e-4 \
--freeze_vision_encoder \
--shuffle_gt_lanes \
--loss_weight_class 2.0 \
--loss_weight_discrete 5.0 \
--loss_weight_bezier 3.0
| Flag | Default | Description |
|---|---|---|
--use_float32 | False | Full float32 training (recommended for RTX 5000) |
--use_qlora | False | 4-bit quantization of the LLM backbone |
--no_lm_loss | False | Skip language modelling loss (~600 MB saved) |
--shuffle_gt_lanes | True | Permutation-invariant GT shuffling |
--freeze_vision_encoder | True | Freeze DINOv3 (only wrapper norm trainable) |
| Component | Approx. VRAM |
|---|---|
| Qwen 1.7B + LoRA (float32) | ~7 GB |
| DINOv3 ViT-B/16 (float32, frozen) | ~1 GB |
| DETR head + mm_projector | ~0.3 GB |
| Gradients + optimizer states | ~6 GB |
| Peak (batch=2, grad_accum=8) | ~15 GB |
python utils/test2d.py \
--checkpoint ./checkpoints/culane/best \
--data_root /path/to/culane_dataset \
--output_dir ./eval_results \
--split test \
--batch_size 4 \
--confidence_threshold 0.5 \
--iou_threshold 0.5
bash scripts/test_culane.sh ./checkpoints/culane/best
Categories: normal, crowd, hlight, shadow, noline, arrow, curve, cross, night.
The evaluator follows the official CULane protocol:
0.5.python utils/inference.py \
--checkpoint_path ./checkpoints/culane/best \
--image_folder /path/to/culane_dataset \
--test_list /path/to/culane_dataset/list/test_gt.txt \
--output_dir ./visualizations \
--num_samples 100 \
--iou_threshold 0.5
Green polylines = ground truth lanes. Red polylines = model predictions.
All coordinates flow through a single CoordinateConverter instance.
| Space | Range | Used For |
|---|---|---|
original | [0, 1640] × [0, 590] px | CULane raw annotations |
normalized | [0, 1] × [0, 1] | Model training & inference |
resized | [0, 608] × [0, 224] px | Vision encoder input |
Rule: GT annotations are parsed in original space, out-of-bounds points are dropped, then everything is normalized before being fed to the model.
| Loss | Weight | Description |
|---|---|---|
class_loss | 2.0 | Binary cross-entropy: lane vs. no-lane |
discrete_loss | 5.0 | L1 on keypoints at GT-valid Y positions + adaptive continuity |
bezier_loss | 3.0 | L1 on Bézier control points |
direction_loss | 1.0 | Cosine similarity of start/end tangent vectors |
smooth_loss | 0.5 | Second-order smoothness on Bézier curves |
lm_loss | 0.1 | Cross-entropy on language modelling tokens (optional) |
# Check coordinate spaces for a single image
python debug_coordinates.py
# Evaluate prediction quality in detail (IoU per lane, template collapse detection)
python utils/diagnose_eval.py \
--checkpoint ./checkpoints/culane/best \
--data_root /path/to/culane_dataset \
--output_dir ./diagnosis \
--num_samples 30
# Verify vision features are diverse across images
python utils/diagnose_vision.py \
--checkpoint_path ./checkpoints/culane/best \
--image_folder /path/to/culane_dataset \
--test_list /path/to/culane_dataset/list/test_gt.txt \
--num_images 5
TODO
Handle illusions — improve robustness to optical illusions and ambiguous lane markings (e.g. shadow patterns, road artifacts, and painted markings that visually resemble lane lines but are not). This may require dedicated augmentation strategies, hard-negative mining, or a post-processing rejection module.
This codebase builds upon:
MIT License — see LICENSE for full terms.
463 commits
Python
96.2%
Shell
3.8%