-
Notifications
You must be signed in to change notification settings - Fork 48
feat(ingestor): extract additional CycloneDX 1.6 model card considerations #2399
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,209 @@ | ||
| { | ||
| "bomFormat": "CycloneDX", | ||
| "components": [ | ||
| { | ||
| "authors": [ | ||
| { | ||
| "name": "nvidia" | ||
| } | ||
| ], | ||
| "bom-ref": "pkg:huggingface/nvidia/canary-1b-v2@1.0", | ||
| "copyright": "NOASSERTION", | ||
| "description": "No description available", | ||
| "externalReferences": [ | ||
| { | ||
| "type": "website", | ||
| "url": "https://huggingface.co/nvidia/canary-1b-v2" | ||
| } | ||
| ], | ||
| "group": "nvidia", | ||
| "licenses": [ | ||
| { | ||
| "license": { | ||
| "id": "unknown", | ||
| "url": "https://spdx.org/licenses/" | ||
| } | ||
| } | ||
| ], | ||
| "manufacturer": { | ||
| "name": "nvidia", | ||
| "url": [ | ||
| "https://huggingface.co/nvidia" | ||
| ] | ||
| }, | ||
| "modelCard": { | ||
| "modelParameters": { | ||
| "architectureFamily": "transformer", | ||
| "inputs": [ | ||
| { | ||
| "format": "text" | ||
| } | ||
| ], | ||
| "modelArchitecture": "canary-1b-v2ForCausalLM", | ||
| "outputs": [ | ||
| { | ||
| "format": "generated-text" | ||
| } | ||
| ], | ||
| "task": "text-generation", | ||
| "approach": { | ||
| "type": "supervised-learning" | ||
| } | ||
| }, | ||
| "quantitativeAnalysis": { | ||
| "graphics": {} | ||
| }, | ||
| "considerations": { | ||
| "technicalLimitations": [ | ||
| "Limited to English text", | ||
| "Max 2048 tokens" | ||
| ], | ||
| "ethicalConsiderations": [ | ||
| { | ||
| "name": "Bias in training data", | ||
| "mitigationStrategy": "Regular auditing of model outputs" | ||
| }, | ||
| { | ||
| "name": "Privacy risks from memorization" | ||
| } | ||
| ], | ||
| "fairnessAssessments": [ | ||
| { | ||
| "groupAtRisk": "Non-English speakers", | ||
| "benefits": "Improved accessibility", | ||
| "harms": "Lower accuracy for underrepresented languages", | ||
| "mitigationStrategy": "Multilingual fine-tuning planned" | ||
| } | ||
| ], | ||
| "performanceTradeoffs": [ | ||
| "Speed vs accuracy on long sequences", | ||
| "Memory usage scales quadratically" | ||
| ], | ||
| "useCases": [ | ||
| "Conversational AI", | ||
| "Document summarization" | ||
| ], | ||
| "users": [ | ||
| "Developers", | ||
| "Researchers" | ||
| ] | ||
| } | ||
| }, | ||
| "name": "canary-1b-v2", | ||
| "publisher": "nvidia", | ||
| "purl": "pkg:huggingface/nvidia/canary-1b-v2@1.0", | ||
| "supplier": { | ||
| "name": "nvidia", | ||
| "url": [ | ||
| "https://huggingface.co/nvidia" | ||
| ] | ||
| }, | ||
| "type": "machine-learning-model", | ||
| "version": "1.0" | ||
| } | ||
| ], | ||
| "dependencies": [ | ||
| { | ||
| "dependsOn": [ | ||
| "pkg:huggingface/nvidia/canary-1b-v2@1.0" | ||
| ], | ||
| "ref": "pkg:generic/nvidia%2Fcanary-1b-v2@1.0" | ||
| } | ||
| ], | ||
| "externalReferences": [ | ||
| { | ||
| "type": "distribution", | ||
| "url": "https://huggingface.co/nvidia/canary-1b-v2" | ||
| } | ||
| ], | ||
| "metadata": { | ||
| "component": { | ||
| "bom-ref": "pkg:generic/nvidia%2Fcanary-1b-v2@1.0", | ||
| "copyright": "NOASSERTION", | ||
| "description": "No description available", | ||
| "name": "canary-1b-v2", | ||
| "purl": "pkg:generic/nvidia%2Fcanary-1b-v2@1.0", | ||
| "type": "application", | ||
| "version": "1.0" | ||
| }, | ||
| "properties": [ | ||
| { | ||
| "name": "primaryPurpose", | ||
| "value": "automatic-speech-recognition" | ||
| }, | ||
| { | ||
| "name": "suppliedBy", | ||
| "value": "nvidia" | ||
| }, | ||
| { | ||
| "name": "typeOfModel", | ||
| "value": "transformer" | ||
| }, | ||
| { | ||
| "name": "bomFormat", | ||
| "value": "CycloneDX" | ||
| }, | ||
| { | ||
| "name": "specVersion", | ||
| "value": "1.6" | ||
| }, | ||
| { | ||
| "name": "serialNumber", | ||
| "value": "urn:uuid:nvidia-canary-1b-v2" | ||
| }, | ||
| { | ||
| "name": "version", | ||
| "value": "1.0.0" | ||
| }, | ||
| { | ||
| "name": "primaryPurpose", | ||
| "value": "automatic-speech-recognition" | ||
| }, | ||
| { | ||
| "name": "suppliedBy", | ||
| "value": "nvidia" | ||
| }, | ||
| { | ||
| "name": "licenses", | ||
| "value": "cc-by-4.0" | ||
| }, | ||
| { | ||
| "name": "limitation", | ||
| "value": "adhered to, & Mitigation | Transcripts and translations may be not 100% accurate" | ||
| }, | ||
| { | ||
| "name": "safetyRiskAssessment", | ||
| "value": "</span>\n\nField | Response\n:---------------------------------------------------:|:----------------------------------\nModel Application(s) | Speech to Text Transcription\nDescribe the life critical impact | None\nUse Case Restrictions | Abide by [CC-BY-4, </span>\n\nField | Response\n:---------------------------------------------------------------------------------------------------|:---------------:\nParticipation considerations from adversely impacted groups [protected classes](https://www, | If a word is not trained in the language model and not presented in vocabulary, the word is not likely to be recognized, & Security, and Privacy Subcards [here](https://developer, | None\n\n## <span style=\"color:#b37800;\">Explainability:</span>\n\nField | Response\n:------------------------------------------------------------------------------------------------------:|:---------------------------------------------------------------------------------:\nIntended Domain | Speech to Text Transcription and Translation\nModel Type | Attention Encoder-Decoder\nIntended Users | This model is intended for developers, researchers, academics, and industries building conversational based applications" | ||
| }, | ||
| { | ||
| "name": "typeOfModel", | ||
| "value": "transformer" | ||
| }, | ||
| { | ||
| "name": "downloadLocation", | ||
| "value": "https://huggingface.co/nvidia/canary-1b-v2/tree/main" | ||
| }, | ||
| { | ||
| "name": "external_references", | ||
| "value": "[{\"type\": \"website\", \"url\": \"https://huggingface.co/nvidia/canary-1b-v2\", \"comment\": \"Model repository\"}, {\"type\": \"distribution\", \"url\": \"https://huggingface.co/nvidia/canary-1b-v2/tree/main\", \"comment\": \"Model files\"}]" | ||
| } | ||
| ], | ||
| "timestamp": "2025-09-26T10:07:22.695451Z", | ||
| "tools": { | ||
| "components": [ | ||
| { | ||
| "bom-ref": "pkg:generic/aetheris-ai/aetheris-aibom-generator@1.0.0", | ||
| "manufacturer": { | ||
| "name": "Aetheris AI" | ||
| }, | ||
| "name": "aetheris-aibom-generator", | ||
| "type": "application", | ||
| "version": "1.0" | ||
| } | ||
| ] | ||
| } | ||
| }, | ||
| "serialNumber": "urn:uuid:test-structured-fields", | ||
| "specVersion": "1.6", | ||
| "version": 1 | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -17,13 +17,118 @@ pub struct ModelCard { | |
|
|
||
| impl From<&Component> for ModelCard { | ||
| fn from(c: &Component) -> Self { | ||
| let properties = Value::from(c.model_card.as_ref().and_then(|card| { | ||
| card.properties.as_ref().map(|v| { | ||
| v.iter() | ||
| .map(|p| (p.name.clone(), p.value.clone().into())) | ||
| .collect::<Map<_, _>>() | ||
| }) | ||
| })); | ||
| let properties = match c.model_card.as_ref() { | ||
| Some(card) => { | ||
| let mut map = Map::new(); | ||
|
|
||
| if let Some(params) = &card.model_parameters { | ||
| if let Some(task) = ¶ms.task { | ||
| map.insert("primaryPurpose".to_string(), Value::String(task.clone())); | ||
| } | ||
| if let Some(approach) = ¶ms.approach | ||
| && let Some(type_val) = &approach.type_ | ||
| { | ||
| map.insert("typeOfModel".to_string(), Value::String(type_val.clone())); | ||
| } | ||
| } | ||
|
|
||
| if let Some(considerations) = &card.considerations { | ||
| if let Some(limitations) = &considerations.technical_limitations | ||
| && !limitations.is_empty() | ||
| { | ||
| map.insert( | ||
| "limitation".to_string(), | ||
| Value::String(limitations.join("; ")), | ||
| ); | ||
| } | ||
|
|
||
| if let Some(risks) = &considerations.ethical_considerations | ||
| && !risks.is_empty() | ||
| { | ||
| let arr: Vec<Value> = risks | ||
| .iter() | ||
| .map(|r| { | ||
| let mut obj = Map::new(); | ||
| if let Some(name) = &r.name { | ||
| obj.insert("name".to_string(), Value::String(name.clone())); | ||
| } | ||
| if let Some(ms) = &r.mitigation_strategy { | ||
| obj.insert( | ||
| "mitigationStrategy".to_string(), | ||
| Value::String(ms.clone()), | ||
| ); | ||
| } | ||
| Value::Object(obj) | ||
| }) | ||
| .collect(); | ||
| map.insert("safetyRiskAssessment".to_string(), Value::Array(arr)); | ||
| } | ||
|
|
||
| if let Some(assessments) = &considerations.fairness_assessments | ||
| && !assessments.is_empty() | ||
| { | ||
| let arr: Vec<Value> = assessments | ||
| .iter() | ||
| .map(|a| { | ||
| let mut obj = Map::new(); | ||
| if let Some(v) = &a.group_at_risk { | ||
| obj.insert("groupAtRisk".to_string(), Value::String(v.clone())); | ||
| } | ||
| if let Some(v) = &a.benefits { | ||
| obj.insert("benefits".to_string(), Value::String(v.clone())); | ||
| } | ||
| if let Some(v) = &a.harms { | ||
| obj.insert("harms".to_string(), Value::String(v.clone())); | ||
| } | ||
| if let Some(v) = &a.mitigation_strategy { | ||
| obj.insert( | ||
| "mitigationStrategy".to_string(), | ||
| Value::String(v.clone()), | ||
| ); | ||
| } | ||
| Value::Object(obj) | ||
| }) | ||
| .collect(); | ||
| map.insert("fairnessAssessments".to_string(), Value::Array(arr)); | ||
| } | ||
|
|
||
| if let Some(tradeoffs) = &considerations.performance_tradeoffs | ||
| && !tradeoffs.is_empty() | ||
| { | ||
| map.insert( | ||
| "performanceTradeoffs".to_string(), | ||
| Value::String(tradeoffs.join("; ")), | ||
| ); | ||
| } | ||
|
|
||
| if let Some(use_cases) = &considerations.use_cases | ||
| && !use_cases.is_empty() | ||
| { | ||
| map.insert("useCases".to_string(), Value::String(use_cases.join("; "))); | ||
| } | ||
|
|
||
| if let Some(users) = &considerations.users | ||
| && !users.is_empty() | ||
| { | ||
| map.insert("users".to_string(), Value::String(users.join("; "))); | ||
| } | ||
| } | ||
|
|
||
| // Generic properties take precedence over structured fields | ||
| if let Some(props) = &card.properties { | ||
| for p in props { | ||
| map.insert(p.name.clone(), p.value.clone().into()); | ||
| } | ||
| } | ||
|
|
||
| if map.is_empty() { | ||
| Value::Null | ||
|
Comment on lines
+124
to
+125
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. issue (bug_risk): Revisit behavior change for model cards with only empty generic properties Previously, an empty |
||
| } else { | ||
| Value::Object(map) | ||
| } | ||
| } | ||
| None => Value::Null, | ||
| }; | ||
| ModelCard { properties } | ||
| } | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion: Consider skipping fully-empty risk objects when all fields are
NoneRight now each
ethical_considerationsentry becomes an object even when bothnameandmitigation_strategyareNone, producing{}entries. This may be unexpected for consumers. Consider either dropping objects that end up empty or only inserting whennameis set, whichever best matches your schema.Suggested implementation:
To fully implement the behavior of skipping fully-empty risk objects, adjust the rest of the closure so that instead of unconditionally returning
Value::Object(obj), it returnsNonewhenobj.is_empty():This should replace the previous
Value::Object(obj)expression at the end of the closure.