Skip to content

Commit 5fd46a3

Browse files
authored
Merge branch 'release/0.9.0' into jihao/cp_5818
2 parents 57eaa9b + edf6258 commit 5fd46a3

File tree

4 files changed

+138
-97
lines changed

4 files changed

+138
-97
lines changed

.github/workflows/detect_broken_links.py

Lines changed: 107 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,15 @@ def find_markdown_files(root_dir: str, logger: logging.Logger) -> List[Path]:
289289
logger.error(error_msg)
290290
raise ValueError(error_msg)
291291

292+
# Patterns for files to skip (attribution files, etc.)
293+
skip_patterns = [
294+
"ATTRIBUTION",
295+
"ATTRIBUTIONS",
296+
"THIRD_PARTY",
297+
"THIRD-PARTY",
298+
"LICENSES",
299+
]
300+
292301
md_files = []
293302

294303
if root_path.is_file():
@@ -302,6 +311,11 @@ def find_markdown_files(root_dir: str, logger: logging.Logger) -> List[Path]:
302311
# If it's a directory, find all .md files recursively
303312
logger.debug(f"Scanning directory recursively: {root_path}")
304313
for file_path in root_path.rglob("*.md"):
314+
# Skip attribution files
315+
file_name_upper = file_path.name.upper()
316+
if any(pattern in file_name_upper for pattern in skip_patterns):
317+
logger.debug(f"Skipping attribution file: {file_path}")
318+
continue
305319
md_files.append(file_path)
306320

307321
logger.info(f"Found {len(md_files)} markdown files in {root_dir}")
@@ -400,15 +414,15 @@ def resolve_link_path(
400414
logger.debug(f"Absolute filesystem path detected: {link_url}")
401415
return Path(link_url)
402416

403-
# Handle symlinks: resolve relative paths from the target's directory, not the symlink's directory
417+
# For symlinks, resolve relative paths from the symlink's location, not the
418+
# target's location. This matches GitHub's behavior where links in symlinked
419+
# files are resolved relative to the symlink
420+
source_dir = source_file.parent
404421
if source_file.is_symlink():
405-
# Get the real path (target) of the symlink
406-
real_source_file = source_file.resolve()
407-
source_dir = real_source_file.parent
408-
logger.debug(f"Source file is a symlink, using target directory: {source_dir}")
409-
else:
410-
# Resolve relative path from the source file's directory
411-
source_dir = source_file.parent
422+
logger.debug(
423+
f"Source file is a symlink, resolving links from symlink location: "
424+
f"{source_dir}"
425+
)
412426

413427
resolved_path = source_dir / link_url
414428

@@ -472,42 +486,96 @@ def validate_links(
472486
link_url, md_file, logger, git_root_dir
473487
)
474488

475-
# Only check if the target should be a markdown file
476-
if is_markdown_file(resolved_path):
489+
# Determine if this is a markdown file, directory, or should be skipped
490+
is_markdown = is_markdown_file(resolved_path)
491+
is_directory_link = link_url.endswith("/") or (
492+
resolved_path.exists() and resolved_path.is_dir()
493+
)
494+
495+
# Check if link has a non-markdown file extension (image, code file, etc.)
496+
has_other_extension = (
497+
resolved_path.suffix
498+
and resolved_path.suffix.lower() not in [".md", ""]
499+
)
500+
501+
# Skip non-markdown files (images, videos, code files, etc.)
502+
# but validate markdown files and directory links (with or without trailing slash)
503+
if not is_markdown and not is_directory_link and has_other_extension:
504+
logger.debug(
505+
f"Skipping non-markdown file link in {md_file}:{line_num} - {link_url}"
506+
)
507+
continue
508+
509+
# Validate the link
510+
is_broken = False
511+
error_reason = None
512+
513+
if is_markdown:
514+
# Check markdown file exists
477515
if not resolved_path.exists():
478-
# Generate GitHub URL for the broken link line
479-
file_for_github = (
480-
path_relative_to_git_root(md_file, git_root_dir, logger)
481-
if git_root_dir
482-
else str(md_file)
483-
)
484-
github_url = (
485-
construct_github_url(
486-
file_for_github, git_info, logger, line_num
487-
)
488-
if git_info
489-
else ""
516+
is_broken = True
517+
error_reason = f"Markdown file does not exist: {resolved_path}"
518+
else:
519+
logger.debug(
520+
f"Valid markdown link in {md_file}:{line_num} - {link_url} -> {resolved_path}"
490521
)
491-
492-
broken_link_info = {
493-
"line": line_num,
494-
"link_text": link_text,
495-
"link_url": link_url,
496-
"resolved_path": str(resolved_path),
497-
"github_url": github_url,
498-
}
499-
broken_links.append(broken_link_info)
500-
total_broken_links += 1
501-
logger.warning(
502-
f"Broken link found in {md_file}:{line_num} - {link_url} -> {resolved_path}"
522+
elif is_directory_link:
523+
# It's a directory link (ends with / or resolves to existing directory)
524+
# Check if directory exists
525+
if not resolved_path.exists():
526+
is_broken = True
527+
error_reason = f"Directory does not exist: {resolved_path}"
528+
elif not resolved_path.is_dir():
529+
is_broken = True
530+
error_reason = (
531+
f"Path exists but is not a directory: {resolved_path}"
503532
)
504533
else:
505534
logger.debug(
506-
f"Valid link in {md_file}:{line_num} - {link_url} -> {resolved_path}"
535+
f"Valid directory link in {md_file}:{line_num} - {link_url} -> {resolved_path}"
507536
)
508537
else:
509-
logger.debug(
510-
f"Skipping non-markdown link in {md_file}:{line_num} - {link_url}"
538+
# Link without extension that's not markdown or directory
539+
# Could be LICENSE, Makefile, etc. - check if it exists
540+
if not resolved_path.exists():
541+
is_broken = True
542+
error_reason = (
543+
f"File does not exist: {resolved_path}. "
544+
f"If this is a directory link, add a trailing slash (/)"
545+
)
546+
else:
547+
logger.debug(
548+
f"Valid file link in {md_file}:{line_num} - {link_url} -> {resolved_path}"
549+
)
550+
551+
# Report broken link if found
552+
if is_broken:
553+
# Generate GitHub URL for the broken link line
554+
file_for_github = (
555+
path_relative_to_git_root(md_file, git_root_dir, logger)
556+
if git_root_dir
557+
else str(md_file)
558+
)
559+
github_url = (
560+
construct_github_url(
561+
file_for_github, git_info, logger, line_num
562+
)
563+
if git_info
564+
else ""
565+
)
566+
567+
broken_link_info = {
568+
"line": line_num,
569+
"link_text": link_text,
570+
"link_url": link_url,
571+
"resolved_path": str(resolved_path),
572+
"error_reason": error_reason,
573+
"github_url": github_url,
574+
}
575+
broken_links.append(broken_link_info)
576+
total_broken_links += 1
577+
logger.warning(
578+
f"Broken link found in {md_file}:{line_num} - {link_url} -> {error_reason}"
511579
)
512580

513581
except Exception as e:
@@ -528,7 +596,8 @@ def validate_links(
528596
"line": line_num,
529597
"link_text": link_text,
530598
"link_url": link_url,
531-
"resolved_path": f"ERROR: {e}",
599+
"resolved_path": "ERROR",
600+
"error_reason": f"Error resolving link: {e}",
532601
"github_url": github_url,
533602
}
534603
broken_links.append(broken_link_info)

.github/workflows/docs-link-check.yml

Lines changed: 20 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -25,56 +25,26 @@ jobs:
2525
key: cache-lychee-${{ github.sha }}
2626
restore-keys: cache-lychee-
2727

28-
# https://github.com/lycheeverse/lychee/issues/1487
29-
- name: Install CA Certificates for lychee
30-
run: |
31-
sudo apt-get install ca-certificates
32-
33-
- name: Install lychee
34-
run: |
35-
set -euo pipefail
36-
mkdir -p "$HOME/.local/bin"
37-
cd "$RUNNER_TEMP"
38-
# TODO: Lychee v0.19.1 doesn't support regex in --exclude-path, so use nightly
39-
# release until there is a released version containing regex support.
40-
curl -sSL -o lychee.tar.gz \
41-
https://github.com/lycheeverse/lychee/releases/download/nightly/lychee-x86_64-unknown-linux-gnu.tar.gz
42-
tar -xzf lychee.tar.gz
43-
BIN_PATH=$(find . -maxdepth 2 -type f -name lychee | head -n1)
44-
install -m 0755 "$BIN_PATH" "$HOME/.local/bin/lychee"
45-
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
46-
lychee --version
47-
4828
- name: Check documentation links with lychee
29+
uses: lycheeverse/lychee-action@a8c4c7cb88f0c7386610c35eb25108e448569cb0 # v2.0.2
30+
with:
31+
args: >-
32+
--cache
33+
--no-progress
34+
--max-retries 2
35+
--retry-wait-time 2
36+
--timeout 20
37+
--root-dir ${{ github.workspace }}
38+
--exclude-path ".*ATTRIBUTIONS.*"
39+
--exclude-path "./lib/llm/tests/data/.*"
40+
--accept "200..=299, 403, 429"
41+
--exclude-all-private
42+
--exclude 0.0.0.0
43+
${{ github.event_name == 'pull_request' && '--offline' || '' }}
44+
.
45+
fail: true
4946
env:
50-
# Set GITHUB_TOKEN to avoid github rate limits on URL checks
5147
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
52-
run: |
53-
set -euo pipefail
54-
55-
# Set offline mode for pull requests, full check for pushes to main
56-
if [[ "${{ github.event_name }}" == "pull_request" ]]; then
57-
echo "Running lychee in offline mode (internal links only) for PR check"
58-
OFFLINE_FLAG="--offline"
59-
else
60-
echo "Running lychee in full mode (all links) for main branch"
61-
OFFLINE_FLAG=""
62-
fi
63-
64-
# Run lychee against all files in repo
65-
lychee \
66-
--cache \
67-
--no-progress \
68-
--max-retries 2 \
69-
--retry-wait-time 2 \
70-
--timeout 20 \
71-
--root-dir "${{ github.workspace }}" \
72-
--exclude-path ".*ATTRIBUTIONS.*" \
73-
--exclude-path "./lib/llm/tests/data/.*" \
74-
--accept "200..=299, 403, 429" \
75-
--exclude-all-private --exclude 0.0.0.0 \
76-
$OFFLINE_FLAG \
77-
.
7848

7949
broken-links-check:
8050
name: Check for broken markdown links
@@ -168,16 +138,18 @@ jobs:
168138
line = link['line']
169139
link_text = link['link_text']
170140
link_url = link['link_url']
141+
error_reason = link.get('error_reason', 'Link target not found')
171142
github_url = link.get('github_url', '')
172143
173144
# Create GitHub annotation for each broken link
174-
annotation_msg = f'Broken link: [{link_text}]({link_url})'
145+
annotation_msg = f'Broken link: [{link_text}]({link_url}) - {error_reason}'
175146
if github_url:
176147
annotation_msg += f' - View: {github_url}'
177148
print(f'::error file={file_path},line={line}::{annotation_msg}')
178149
179150
# Display in workflow output
180151
print(f' {i}. Line {line}: [{link_text}]({link_url})')
152+
print(f' ❌ {error_reason}')
181153
if github_url:
182154
print(f' 🔗 View on GitHub: {github_url}')
183155
else:

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -195,9 +195,9 @@ Pre-built deployment configurations for common models and topologies:
195195

196196
| Model | Framework | Mode | GPUs | Recipe |
197197
|-------|-----------|------|------|--------|
198-
| Llama-3.1-70B | vLLM | Aggregated | 4x H100 | [View](recipes/vllm/llama-3.1-70b/) |
199-
| DeepSeek-R1 | SGLang | Disaggregated | 8x H200 | [View](recipes/sglang/deepseek-r1/) |
200-
| Qwen3-32B | TensorRT-LLM | Disaggregated | 8x GPU | [View](recipes/trtllm/qwen3-32b/) |
198+
| Llama-3-70B | vLLM | Aggregated | 4x H100 | [View](recipes/llama-3-70b/vllm/) |
199+
| DeepSeek-R1 | SGLang | Disaggregated | 8x H200 | [View](recipes/deepseek-r1/sglang/) |
200+
| Qwen3-32B-FP8 | TensorRT-LLM | Aggregated | 8x GPU | [View](recipes/qwen3-32b-fp8/trtllm/) |
201201

202202
See [recipes/README.md](recipes/README.md) for the full list and deployment instructions.
203203

examples/README.md

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -34,19 +34,19 @@ Learn fundamental Dynamo concepts through these introductory examples:
3434

3535
These examples show how Dynamo broadly works using major inference engines.
3636

37-
If you want to see advanced, framework-specific deployment patterns and best practices, check out the [Examples Backends](../examples/backends/) directory:
38-
- **[vLLM](backends/vllm/)** – vLLM-specific deployment and configuration
39-
- **[SGLang](backends/sglang/)** – SGLang integration examples and workflows
40-
- **[TensorRT-LLM](backends/trtllm/)** – TensorRT-LLM workflows and optimizations
37+
If you want to see advanced, framework-specific deployment patterns and best practices, check out the [Examples Backends](/examples/backends/) directory:
38+
- **[vLLM](/examples/backends/vllm/)** – vLLM-specific deployment and configuration
39+
- **[SGLang](/examples/backends/sglang/)** – SGLang integration examples and workflows
40+
- **[TensorRT-LLM](/examples/backends/trtllm/)** – TensorRT-LLM workflows and optimizations
4141

4242
## Deployment Examples
4343

4444
Platform-specific deployment guides for production environments:
4545

46-
- **[Amazon EKS](deployments/EKS/)** - Deploy Dynamo on Amazon Elastic Kubernetes Service
47-
- **[Azure AKS](deployments/AKS/)** - Deploy Dynamo on Azure Kubernetes Service
48-
- **[Amazon ECS](deployments/ECS/)** - Deploy Dynamo on Amazon Elastic Container Service
49-
- **[Router Standalone](deployments/router_standalone/)** - Standalone router deployment patterns
46+
- **[Amazon EKS](/examples/deployments/EKS/)** - Deploy Dynamo on Amazon Elastic Kubernetes Service
47+
- **[Azure AKS](/examples/deployments/AKS/)** - Deploy Dynamo on Azure Kubernetes Service
48+
- **[Amazon ECS](/examples/deployments/ECS/)** - Deploy Dynamo on Amazon Elastic Container Service
49+
- **[Router Standalone](/examples/deployments/router_standalone/)** - Standalone router deployment patterns
5050
- **Google GKE** - _Coming soon_
5151

5252
## Runtime Examples

0 commit comments

Comments
 (0)